feat(admin-center): implement admin center settings retrieval and enhance holiday population logic
- Added a new API endpoint in AdminCenterController to fetch admin center settings, including organization details. - Updated the admin center API router to include the new settings route. - Enhanced the holiday controller to check for recent holiday population before attempting to repopulate, preventing duplicate entries. - Improved the holiday calendar component to manage holiday population attempts and display loading states. - Updated localization files to support new messages related to calculation methods and holiday settings.
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
"saveButton": "Save",
|
||||
"jobTitleColumn": "Job Title",
|
||||
"ratePerHourColumn": "Rate per hour",
|
||||
"ratePerManDayColumn": "Rate per man day",
|
||||
"ratePerManDayColumn": "Tarifa për ditë-njeri",
|
||||
"calculationMethodText": "Calculation Method",
|
||||
"hourlyRatesText": "Hourly Rates",
|
||||
"manDaysText": "Man Days",
|
||||
|
||||
@@ -48,5 +48,8 @@
|
||||
"jobTitleCreatedSuccess": "Titulli i punës u krijua me sukses",
|
||||
"jobTitleCreateError": "Dështoi të krijohet titulli i punës",
|
||||
"createButton": "Krijo",
|
||||
"cancelButton": "Anulo"
|
||||
"cancelButton": "Anulo",
|
||||
"discardButton": "Hidh poshtë",
|
||||
"manDaysCalculationMessage": "Organizata po përdor llogaritjen e ditëve-njeri ({{hours}}h/ditë). Tarifat më sipër përfaqësojnë tarifa ditore.",
|
||||
"hourlyCalculationMessage": "Organizata po përdor llogaritjen orore. Tarifat më sipër përfaqësojnë tarifa orore."
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"saveButton": "Save",
|
||||
"jobTitleColumn": "Job Title",
|
||||
"ratePerHourColumn": "Rate per hour",
|
||||
"ratePerManDayColumn": "Rate per man day",
|
||||
"ratePerManDayColumn": "Satz pro Manntag",
|
||||
"calculationMethodText": "Calculation Method",
|
||||
"hourlyRatesText": "Hourly Rates",
|
||||
"manDaysText": "Man Days",
|
||||
|
||||
@@ -48,5 +48,8 @@
|
||||
"jobTitleCreatedSuccess": "Berufsbezeichnung erfolgreich erstellt",
|
||||
"jobTitleCreateError": "Berufsbezeichnung konnte nicht erstellt werden",
|
||||
"createButton": "Erstellen",
|
||||
"cancelButton": "Abbrechen"
|
||||
"cancelButton": "Abbrechen",
|
||||
"discardButton": "Verwerfen",
|
||||
"manDaysCalculationMessage": "Organisation verwendet Manntage-Berechnung ({{hours}}h/Tag). Die obigen Sätze stellen Tagessätze dar.",
|
||||
"hourlyCalculationMessage": "Organisation verwendet Stunden-Berechnung. Die obigen Sätze stellen Stundensätze dar."
|
||||
}
|
||||
|
||||
@@ -48,5 +48,8 @@
|
||||
"jobTitleCreatedSuccess": "Job title created successfully",
|
||||
"jobTitleCreateError": "Failed to create job title",
|
||||
"createButton": "Create",
|
||||
"cancelButton": "Cancel"
|
||||
"cancelButton": "Cancel",
|
||||
"discardButton": "Discard",
|
||||
"manDaysCalculationMessage": "Organization is using man days calculation ({{hours}}h/day). Rates above represent daily rates.",
|
||||
"hourlyCalculationMessage": "Organization is using hourly calculation. Rates above represent hourly rates."
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"saveButton": "Save",
|
||||
"jobTitleColumn": "Job Title",
|
||||
"ratePerHourColumn": "Rate per hour",
|
||||
"ratePerManDayColumn": "Rate per man day",
|
||||
"ratePerManDayColumn": "Tarifa por día-hombre",
|
||||
"calculationMethodText": "Calculation Method",
|
||||
"hourlyRatesText": "Hourly Rates",
|
||||
"manDaysText": "Man Days",
|
||||
|
||||
@@ -48,5 +48,8 @@
|
||||
"jobTitleCreatedSuccess": "Título de trabajo creado con éxito",
|
||||
"jobTitleCreateError": "No se pudo crear el título de trabajo",
|
||||
"createButton": "Crear",
|
||||
"cancelButton": "Cancelar"
|
||||
"cancelButton": "Cancelar",
|
||||
"discardButton": "Descartar",
|
||||
"manDaysCalculationMessage": "La organización utiliza cálculo por días-hombre ({{hours}}h/día). Las tarifas anteriores representan tarifas diarias.",
|
||||
"hourlyCalculationMessage": "La organización utiliza cálculo por horas. Las tarifas anteriores representan tarifas por hora."
|
||||
}
|
||||
|
||||
@@ -1,114 +1,114 @@
|
||||
{
|
||||
"financeText": "Finance",
|
||||
"ratecardSingularText": "Rate Card",
|
||||
"groupByText": "Group by",
|
||||
"financeText": "Finanças",
|
||||
"ratecardSingularText": "Tabela de Preços",
|
||||
"groupByText": "Agrupar por",
|
||||
"statusText": "Status",
|
||||
"phaseText": "Phase",
|
||||
"priorityText": "Priority",
|
||||
"exportButton": "Export",
|
||||
"currencyText": "Currency",
|
||||
"importButton": "Import",
|
||||
"filterText": "Filter",
|
||||
"billableOnlyText": "Billable Only",
|
||||
"nonBillableOnlyText": "Non-Billable Only",
|
||||
"allTasksText": "All Tasks",
|
||||
"projectBudgetOverviewText": "Project Budget Overview",
|
||||
"taskColumn": "Task",
|
||||
"membersColumn": "Members",
|
||||
"hoursColumn": "Estimated Hours",
|
||||
"manDaysColumn": "Estimated Man Days",
|
||||
"actualManDaysColumn": "Actual Man Days",
|
||||
"effortVarianceColumn": "Effort Variance",
|
||||
"totalTimeLoggedColumn": "Total Time Logged",
|
||||
"costColumn": "Actual Cost",
|
||||
"estimatedCostColumn": "Estimated Cost",
|
||||
"fixedCostColumn": "Fixed Cost",
|
||||
"totalBudgetedCostColumn": "Total Budgeted Cost",
|
||||
"totalActualCostColumn": "Total Actual Cost",
|
||||
"varianceColumn": "Variance",
|
||||
"phaseText": "Fase",
|
||||
"priorityText": "Prioridade",
|
||||
"exportButton": "Exportar",
|
||||
"currencyText": "Moeda",
|
||||
"importButton": "Importar",
|
||||
"filterText": "Filtrar",
|
||||
"billableOnlyText": "Apenas Faturável",
|
||||
"nonBillableOnlyText": "Apenas Não Faturável",
|
||||
"allTasksText": "Todas as Tarefas",
|
||||
"projectBudgetOverviewText": "Visão Geral do Orçamento do Projeto",
|
||||
"taskColumn": "Tarefa",
|
||||
"membersColumn": "Membros",
|
||||
"hoursColumn": "Horas Estimadas",
|
||||
"manDaysColumn": "Dias-Homem Estimados",
|
||||
"actualManDaysColumn": "Dias-Homem Reais",
|
||||
"effortVarianceColumn": "Variação do Esforço",
|
||||
"totalTimeLoggedColumn": "Tempo Total Registrado",
|
||||
"costColumn": "Custo Real",
|
||||
"estimatedCostColumn": "Custo Estimado",
|
||||
"fixedCostColumn": "Custo Fixo",
|
||||
"totalBudgetedCostColumn": "Custo Total Orçado",
|
||||
"totalActualCostColumn": "Custo Total Real",
|
||||
"varianceColumn": "Variação",
|
||||
"totalText": "Total",
|
||||
"noTasksFound": "No tasks found",
|
||||
"addRoleButton": "+ Add Role",
|
||||
"ratecardImportantNotice": "* This rate card is generated based on the company's standard job titles and rates. However, you have the flexibility to modify it according to the project. These changes will not impact the organization's standard job titles and rates.",
|
||||
"saveButton": "Save",
|
||||
"jobTitleColumn": "Job Title",
|
||||
"ratePerHourColumn": "Rate per hour",
|
||||
"ratePerManDayColumn": "Rate per man day",
|
||||
"calculationMethodText": "Calculation Method",
|
||||
"hourlyRatesText": "Hourly Rates",
|
||||
"manDaysText": "Man Days",
|
||||
"hoursPerDayText": "Hours per Day",
|
||||
"ratecardPluralText": "Rate Cards",
|
||||
"labourHoursColumn": "Labour Hours",
|
||||
"actions": "Actions",
|
||||
"selectJobTitle": "Select Job Title",
|
||||
"ratecardsPluralText": "Rate Card Templates",
|
||||
"deleteConfirm": "Are you sure ?",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"alreadyImportedRateCardMessage": "A rate card has already been imported. Clear all imported rate cards to add a new one.",
|
||||
"noTasksFound": "Nenhuma tarefa encontrada",
|
||||
"addRoleButton": "+ Adicionar Função",
|
||||
"ratecardImportantNotice": "* Esta tabela de preços é gerada com base nos cargos padrão e taxas da empresa. No entanto, você tem a flexibilidade de modificá-la de acordo com o projeto. Essas alterações não afetarão os cargos padrão e taxas da organização.",
|
||||
"saveButton": "Salvar",
|
||||
"jobTitleColumn": "Cargo",
|
||||
"ratePerHourColumn": "Taxa por hora",
|
||||
"ratePerManDayColumn": "Taxa por dia-homem",
|
||||
"calculationMethodText": "Método de Cálculo",
|
||||
"hourlyRatesText": "Taxas por Hora",
|
||||
"manDaysText": "Dias-Homem",
|
||||
"hoursPerDayText": "Horas por Dia",
|
||||
"ratecardPluralText": "Tabelas de Preços",
|
||||
"labourHoursColumn": "Horas de Trabalho",
|
||||
"actions": "Ações",
|
||||
"selectJobTitle": "Selecionar Cargo",
|
||||
"ratecardsPluralText": "Modelos de Tabela de Preços",
|
||||
"deleteConfirm": "Tem certeza?",
|
||||
"yes": "Sim",
|
||||
"no": "Não",
|
||||
"alreadyImportedRateCardMessage": "Uma tabela de preços já foi importada. Limpe todas as tabelas de preços importadas para adicionar uma nova.",
|
||||
"budgetOverviewTooltips": {
|
||||
"manualBudget": "Manual project budget amount set by project manager",
|
||||
"totalActualCost": "Total actual cost including fixed costs",
|
||||
"variance": "Difference between manual budget and actual cost",
|
||||
"utilization": "Percentage of manual budget utilized",
|
||||
"estimatedHours": "Total estimated hours from all tasks",
|
||||
"fixedCosts": "Total fixed costs from all tasks",
|
||||
"timeBasedCost": "Actual cost from time tracking (excluding fixed costs)",
|
||||
"remainingBudget": "Remaining budget amount"
|
||||
"manualBudget": "Valor do orçamento manual do projeto definido pelo gerente do projeto",
|
||||
"totalActualCost": "Custo total real incluindo custos fixos",
|
||||
"variance": "Diferença entre orçamento manual e custo real",
|
||||
"utilization": "Porcentagem do orçamento manual utilizado",
|
||||
"estimatedHours": "Total de horas estimadas de todas as tarefas",
|
||||
"fixedCosts": "Total de custos fixos de todas as tarefas",
|
||||
"timeBasedCost": "Custo real do rastreamento de tempo (excluindo custos fixos)",
|
||||
"remainingBudget": "Valor do orçamento restante"
|
||||
},
|
||||
"budgetModal": {
|
||||
"title": "Edit Project Budget",
|
||||
"description": "Set a manual budget for this project. This budget will be used for all financial calculations and should include both time-based costs and fixed costs.",
|
||||
"placeholder": "Enter budget amount",
|
||||
"saveButton": "Save",
|
||||
"cancelButton": "Cancel"
|
||||
"title": "Editar Orçamento do Projeto",
|
||||
"description": "Defina um orçamento manual para este projeto. Este orçamento será usado para todos os cálculos financeiros e deve incluir tanto custos baseados em tempo quanto custos fixos.",
|
||||
"placeholder": "Digite o valor do orçamento",
|
||||
"saveButton": "Salvar",
|
||||
"cancelButton": "Cancelar"
|
||||
},
|
||||
"budgetStatistics": {
|
||||
"manualBudget": "Manual Budget",
|
||||
"totalActualCost": "Total Actual Cost",
|
||||
"variance": "Variance",
|
||||
"budgetUtilization": "Budget Utilization",
|
||||
"estimatedHours": "Estimated Hours",
|
||||
"fixedCosts": "Fixed Costs",
|
||||
"timeBasedCost": "Time-based Cost",
|
||||
"remainingBudget": "Remaining Budget",
|
||||
"noManualBudgetSet": "(No Manual Budget Set)"
|
||||
"manualBudget": "Orçamento Manual",
|
||||
"totalActualCost": "Custo Total Real",
|
||||
"variance": "Variação",
|
||||
"budgetUtilization": "Utilização do Orçamento",
|
||||
"estimatedHours": "Horas Estimadas",
|
||||
"fixedCosts": "Custos Fixos",
|
||||
"timeBasedCost": "Custo Baseado em Tempo",
|
||||
"remainingBudget": "Orçamento Restante",
|
||||
"noManualBudgetSet": "(Nenhum Orçamento Manual Definido)"
|
||||
},
|
||||
"budgetSettingsDrawer": {
|
||||
"title": "Project Budget Settings",
|
||||
"budgetConfiguration": "Budget Configuration",
|
||||
"projectBudget": "Project Budget",
|
||||
"projectBudgetTooltip": "Total budget allocated for this project",
|
||||
"currency": "Currency",
|
||||
"costCalculationMethod": "Cost Calculation Method",
|
||||
"calculationMethod": "Calculation Method",
|
||||
"workingHoursPerDay": "Working Hours per Day",
|
||||
"workingHoursPerDayTooltip": "Number of working hours in a day for man-day calculations",
|
||||
"hourlyCalculationInfo": "Costs will be calculated using estimated hours × hourly rates",
|
||||
"manDaysCalculationInfo": "Costs will be calculated using estimated man days × daily rates",
|
||||
"importantNotes": "Important Notes",
|
||||
"calculationMethodChangeNote": "• Changing the calculation method will affect how costs are calculated for all tasks in this project",
|
||||
"immediateEffectNote": "• Changes take effect immediately and will recalculate all project totals",
|
||||
"projectWideNote": "• Budget settings apply to the entire project and all its tasks",
|
||||
"cancel": "Cancel",
|
||||
"saveChanges": "Save Changes",
|
||||
"budgetSettingsUpdated": "Budget settings updated successfully",
|
||||
"budgetSettingsUpdateFailed": "Failed to update budget settings"
|
||||
"title": "Configurações de Orçamento do Projeto",
|
||||
"budgetConfiguration": "Configuração do Orçamento",
|
||||
"projectBudget": "Orçamento do Projeto",
|
||||
"projectBudgetTooltip": "Orçamento total alocado para este projeto",
|
||||
"currency": "Moeda",
|
||||
"costCalculationMethod": "Método de Cálculo de Custo",
|
||||
"calculationMethod": "Método de Cálculo",
|
||||
"workingHoursPerDay": "Horas de Trabalho por Dia",
|
||||
"workingHoursPerDayTooltip": "Número de horas de trabalho em um dia para cálculos de dia-homem",
|
||||
"hourlyCalculationInfo": "Os custos serão calculados usando horas estimadas × taxas por hora",
|
||||
"manDaysCalculationInfo": "Os custos serão calculados usando dias-homem estimados × taxas diárias",
|
||||
"importantNotes": "Notas Importantes",
|
||||
"calculationMethodChangeNote": "• Alterar o método de cálculo afetará como os custos são calculados para todas as tarefas neste projeto",
|
||||
"immediateEffectNote": "• As alterações entram em vigor imediatamente e recalcularão todos os totais do projeto",
|
||||
"projectWideNote": "• As configurações de orçamento se aplicam a todo o projeto e todas as suas tarefas",
|
||||
"cancel": "Cancelar",
|
||||
"saveChanges": "Salvar Alterações",
|
||||
"budgetSettingsUpdated": "Configurações de orçamento atualizadas com sucesso",
|
||||
"budgetSettingsUpdateFailed": "Falha ao atualizar configurações de orçamento"
|
||||
},
|
||||
"columnTooltips": {
|
||||
"hours": "Total estimated hours for all tasks. Calculated from task time estimates.",
|
||||
"manDays": "Total estimated man days for all tasks. Based on {{hoursPerDay}} hours per working day.",
|
||||
"actualManDays": "Actual man days spent based on time logged. Calculated as: Total Time Logged ÷ {{hoursPerDay}} hours per day.",
|
||||
"effortVariance": "Difference between estimated and actual man days. Positive values indicate over-estimation, negative values indicate under-estimation.",
|
||||
"totalTimeLogged": "Total time actually logged by team members across all tasks.",
|
||||
"estimatedCostHourly": "Estimated cost calculated as: Estimated Hours × Hourly Rates for assigned team members.",
|
||||
"estimatedCostManDays": "Estimated cost calculated as: Estimated Man Days × Daily Rates for assigned team members.",
|
||||
"actualCost": "Actual cost based on time logged. Calculated as: Time Logged × Hourly Rates for team members.",
|
||||
"fixedCost": "Fixed costs that don't depend on time spent. Added manually per task.",
|
||||
"totalBudgetHourly": "Total budgeted cost including estimated cost (Hours × Hourly Rates) + Fixed Costs.",
|
||||
"totalBudgetManDays": "Total budgeted cost including estimated cost (Man Days × Daily Rates) + Fixed Costs.",
|
||||
"totalActual": "Total actual cost including time-based cost + Fixed Costs.",
|
||||
"variance": "Cost variance: Total Budgeted Costs - Total Actual Cost. Positive values indicate under-budget, negative values indicate over-budget."
|
||||
"hours": "Total de horas estimadas para todas as tarefas. Calculado a partir das estimativas de tempo das tarefas.",
|
||||
"manDays": "Total de dias-homem estimados para todas as tarefas. Baseado em {{hoursPerDay}} horas por dia de trabalho.",
|
||||
"actualManDays": "Dias-homem reais gastos com base no tempo registrado. Calculado como: Tempo Total Registrado ÷ {{hoursPerDay}} horas por dia.",
|
||||
"effortVariance": "Diferença entre dias-homem estimados e reais. Valores positivos indicam superestimação, valores negativos indicam subestimação.",
|
||||
"totalTimeLogged": "Tempo total realmente registrado pelos membros da equipe em todas as tarefas.",
|
||||
"estimatedCostHourly": "Custo estimado calculado como: Horas Estimadas × Taxas por Hora para membros da equipe designados.",
|
||||
"estimatedCostManDays": "Custo estimado calculado como: Dias-Homem Estimados × Taxas Diárias para membros da equipe designados.",
|
||||
"actualCost": "Custo real baseado no tempo registrado. Calculado como: Tempo Registrado × Taxas por Hora para membros da equipe.",
|
||||
"fixedCost": "Custos fixos que não dependem do tempo gasto. Adicionados manualmente por tarefa.",
|
||||
"totalBudgetHourly": "Custo total orçado incluindo custo estimado (Horas × Taxas por Hora) + Custos Fixos.",
|
||||
"totalBudgetManDays": "Custo total orçado incluindo custo estimado (Dias-Homem × Taxas Diárias) + Custos Fixos.",
|
||||
"totalActual": "Custo total real incluindo custo baseado em tempo + Custos Fixos.",
|
||||
"variance": "Variação de custo: Custos Totais Orçados - Custo Total Real. Valores positivos indicam abaixo do orçamento, valores negativos indicam acima do orçamento."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,5 +48,8 @@
|
||||
"jobTitleCreatedSuccess": "Cargo criado com sucesso",
|
||||
"jobTitleCreateError": "Falha ao criar cargo",
|
||||
"createButton": "Criar",
|
||||
"cancelButton": "Cancelar"
|
||||
"cancelButton": "Cancelar",
|
||||
"discardButton": "Descartar",
|
||||
"manDaysCalculationMessage": "A organização está usando cálculo por dias-homem ({{hours}}h/dia). As taxas acima representam taxas diárias.",
|
||||
"hourlyCalculationMessage": "A organização está usando cálculo por horas. As taxas acima representam taxas horárias."
|
||||
}
|
||||
|
||||
@@ -1,114 +1,114 @@
|
||||
{
|
||||
"financeText": "Finance",
|
||||
"ratecardSingularText": "Rate Card",
|
||||
"groupByText": "Group by",
|
||||
"statusText": "Status",
|
||||
"phaseText": "Phase",
|
||||
"priorityText": "Priority",
|
||||
"exportButton": "Export",
|
||||
"currencyText": "Currency",
|
||||
"importButton": "Import",
|
||||
"filterText": "Filter",
|
||||
"billableOnlyText": "Billable Only",
|
||||
"nonBillableOnlyText": "Non-Billable Only",
|
||||
"allTasksText": "All Tasks",
|
||||
"projectBudgetOverviewText": "Project Budget Overview",
|
||||
"taskColumn": "Task",
|
||||
"membersColumn": "Members",
|
||||
"hoursColumn": "Estimated Hours",
|
||||
"manDaysColumn": "Estimated Man Days",
|
||||
"actualManDaysColumn": "Actual Man Days",
|
||||
"effortVarianceColumn": "Effort Variance",
|
||||
"totalTimeLoggedColumn": "Total Time Logged",
|
||||
"costColumn": "Actual Cost",
|
||||
"estimatedCostColumn": "Estimated Cost",
|
||||
"fixedCostColumn": "Fixed Cost",
|
||||
"totalBudgetedCostColumn": "Total Budgeted Cost",
|
||||
"totalActualCostColumn": "Total Actual Cost",
|
||||
"varianceColumn": "Variance",
|
||||
"totalText": "Total",
|
||||
"noTasksFound": "No tasks found",
|
||||
"addRoleButton": "+ Add Role",
|
||||
"ratecardImportantNotice": "* This rate card is generated based on the company's standard job titles and rates. However, you have the flexibility to modify it according to the project. These changes will not impact the organization's standard job titles and rates.",
|
||||
"saveButton": "Save",
|
||||
"jobTitleColumn": "Job Title",
|
||||
"ratePerHourColumn": "Rate per hour",
|
||||
"ratePerManDayColumn": "Rate per man day",
|
||||
"calculationMethodText": "Calculation Method",
|
||||
"hourlyRatesText": "Hourly Rates",
|
||||
"manDaysText": "Man Days",
|
||||
"hoursPerDayText": "Hours per Day",
|
||||
"ratecardPluralText": "Rate Cards",
|
||||
"labourHoursColumn": "Labour Hours",
|
||||
"actions": "Actions",
|
||||
"selectJobTitle": "Select Job Title",
|
||||
"ratecardsPluralText": "Rate Card Templates",
|
||||
"deleteConfirm": "Are you sure ?",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"alreadyImportedRateCardMessage": "A rate card has already been imported. Clear all imported rate cards to add a new one.",
|
||||
"financeText": "财务",
|
||||
"ratecardSingularText": "费率卡",
|
||||
"groupByText": "分组方式",
|
||||
"statusText": "状态",
|
||||
"phaseText": "阶段",
|
||||
"priorityText": "优先级",
|
||||
"exportButton": "导出",
|
||||
"currencyText": "货币",
|
||||
"importButton": "导入",
|
||||
"filterText": "筛选",
|
||||
"billableOnlyText": "仅可计费",
|
||||
"nonBillableOnlyText": "仅不可计费",
|
||||
"allTasksText": "所有任务",
|
||||
"projectBudgetOverviewText": "项目预算概览",
|
||||
"taskColumn": "任务",
|
||||
"membersColumn": "成员",
|
||||
"hoursColumn": "预估工时",
|
||||
"manDaysColumn": "预估人天",
|
||||
"actualManDaysColumn": "实际人天",
|
||||
"effortVarianceColumn": "工作量偏差",
|
||||
"totalTimeLoggedColumn": "总记录时间",
|
||||
"costColumn": "实际成本",
|
||||
"estimatedCostColumn": "预估成本",
|
||||
"fixedCostColumn": "固定成本",
|
||||
"totalBudgetedCostColumn": "总预算成本",
|
||||
"totalActualCostColumn": "总实际成本",
|
||||
"varianceColumn": "偏差",
|
||||
"totalText": "总计",
|
||||
"noTasksFound": "未找到任务",
|
||||
"addRoleButton": "+ 添加角色",
|
||||
"ratecardImportantNotice": "* 此费率卡基于公司标准职位和费率生成。但是,您可以根据项目灵活修改。这些更改不会影响组织的标准职位和费率。",
|
||||
"saveButton": "保存",
|
||||
"jobTitleColumn": "职位",
|
||||
"ratePerHourColumn": "每小时费率",
|
||||
"ratePerManDayColumn": "每人每日费率",
|
||||
"calculationMethodText": "计算方法",
|
||||
"hourlyRatesText": "小时费率",
|
||||
"manDaysText": "人天",
|
||||
"hoursPerDayText": "每日工时",
|
||||
"ratecardPluralText": "费率卡",
|
||||
"labourHoursColumn": "工时",
|
||||
"actions": "操作",
|
||||
"selectJobTitle": "选择职位",
|
||||
"ratecardsPluralText": "费率卡模板",
|
||||
"deleteConfirm": "您确定吗?",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"alreadyImportedRateCardMessage": "已导入费率卡。清除所有已导入的费率卡以添加新的费率卡。",
|
||||
"budgetOverviewTooltips": {
|
||||
"manualBudget": "Manual project budget amount set by project manager",
|
||||
"totalActualCost": "Total actual cost including fixed costs",
|
||||
"variance": "Difference between manual budget and actual cost",
|
||||
"utilization": "Percentage of manual budget utilized",
|
||||
"estimatedHours": "Total estimated hours from all tasks",
|
||||
"fixedCosts": "Total fixed costs from all tasks",
|
||||
"timeBasedCost": "Actual cost from time tracking (excluding fixed costs)",
|
||||
"remainingBudget": "Remaining budget amount"
|
||||
"manualBudget": "项目经理设置的手动项目预算金额",
|
||||
"totalActualCost": "包括固定成本在内的总实际成本",
|
||||
"variance": "手动预算与实际成本之间的差异",
|
||||
"utilization": "手动预算使用百分比",
|
||||
"estimatedHours": "所有任务的总预估工时",
|
||||
"fixedCosts": "所有任务的总固定成本",
|
||||
"timeBasedCost": "基于时间跟踪的实际成本(不包括固定成本)",
|
||||
"remainingBudget": "剩余预算金额"
|
||||
},
|
||||
"budgetModal": {
|
||||
"title": "Edit Project Budget",
|
||||
"description": "Set a manual budget for this project. This budget will be used for all financial calculations and should include both time-based costs and fixed costs.",
|
||||
"placeholder": "Enter budget amount",
|
||||
"saveButton": "Save",
|
||||
"cancelButton": "Cancel"
|
||||
"title": "编辑项目预算",
|
||||
"description": "为此项目设置手动预算。此预算将用于所有财务计算,应包括基于时间的成本和固定成本。",
|
||||
"placeholder": "输入预算金额",
|
||||
"saveButton": "保存",
|
||||
"cancelButton": "取消"
|
||||
},
|
||||
"budgetStatistics": {
|
||||
"manualBudget": "Manual Budget",
|
||||
"totalActualCost": "Total Actual Cost",
|
||||
"variance": "Variance",
|
||||
"budgetUtilization": "Budget Utilization",
|
||||
"estimatedHours": "Estimated Hours",
|
||||
"fixedCosts": "Fixed Costs",
|
||||
"timeBasedCost": "Time-based Cost",
|
||||
"remainingBudget": "Remaining Budget",
|
||||
"noManualBudgetSet": "(No Manual Budget Set)"
|
||||
"manualBudget": "手动预算",
|
||||
"totalActualCost": "总实际成本",
|
||||
"variance": "偏差",
|
||||
"budgetUtilization": "预算使用率",
|
||||
"estimatedHours": "预估工时",
|
||||
"fixedCosts": "固定成本",
|
||||
"timeBasedCost": "基于时间的成本",
|
||||
"remainingBudget": "剩余预算",
|
||||
"noManualBudgetSet": "(未设置手动预算)"
|
||||
},
|
||||
"budgetSettingsDrawer": {
|
||||
"title": "Project Budget Settings",
|
||||
"budgetConfiguration": "Budget Configuration",
|
||||
"projectBudget": "Project Budget",
|
||||
"projectBudgetTooltip": "Total budget allocated for this project",
|
||||
"currency": "Currency",
|
||||
"costCalculationMethod": "Cost Calculation Method",
|
||||
"calculationMethod": "Calculation Method",
|
||||
"workingHoursPerDay": "Working Hours per Day",
|
||||
"workingHoursPerDayTooltip": "Number of working hours in a day for man-day calculations",
|
||||
"hourlyCalculationInfo": "Costs will be calculated using estimated hours × hourly rates",
|
||||
"manDaysCalculationInfo": "Costs will be calculated using estimated man days × daily rates",
|
||||
"importantNotes": "Important Notes",
|
||||
"calculationMethodChangeNote": "• Changing the calculation method will affect how costs are calculated for all tasks in this project",
|
||||
"immediateEffectNote": "• Changes take effect immediately and will recalculate all project totals",
|
||||
"projectWideNote": "• Budget settings apply to the entire project and all its tasks",
|
||||
"cancel": "Cancel",
|
||||
"saveChanges": "Save Changes",
|
||||
"budgetSettingsUpdated": "Budget settings updated successfully",
|
||||
"budgetSettingsUpdateFailed": "Failed to update budget settings"
|
||||
"title": "项目预算设置",
|
||||
"budgetConfiguration": "预算配置",
|
||||
"projectBudget": "项目预算",
|
||||
"projectBudgetTooltip": "为此项目分配的总预算",
|
||||
"currency": "货币",
|
||||
"costCalculationMethod": "成本计算方法",
|
||||
"calculationMethod": "计算方法",
|
||||
"workingHoursPerDay": "每日工作工时",
|
||||
"workingHoursPerDayTooltip": "人天计算中每日的工作工时数",
|
||||
"hourlyCalculationInfo": "成本将使用预估工时 × 小时费率计算",
|
||||
"manDaysCalculationInfo": "成本将使用预估人天 × 日费率计算",
|
||||
"importantNotes": "重要说明",
|
||||
"calculationMethodChangeNote": "• 更改计算方法将影响此项目中所有任务的成本计算方式",
|
||||
"immediateEffectNote": "• 更改立即生效并将重新计算所有项目总计",
|
||||
"projectWideNote": "• 预算设置适用于整个项目及其所有任务",
|
||||
"cancel": "取消",
|
||||
"saveChanges": "保存更改",
|
||||
"budgetSettingsUpdated": "预算设置更新成功",
|
||||
"budgetSettingsUpdateFailed": "更新预算设置失败"
|
||||
},
|
||||
"columnTooltips": {
|
||||
"hours": "Total estimated hours for all tasks. Calculated from task time estimates.",
|
||||
"manDays": "Total estimated man days for all tasks. Based on {{hoursPerDay}} hours per working day.",
|
||||
"actualManDays": "Actual man days spent based on time logged. Calculated as: Total Time Logged ÷ {{hoursPerDay}} hours per day.",
|
||||
"effortVariance": "Difference between estimated and actual man days. Positive values indicate over-estimation, negative values indicate under-estimation.",
|
||||
"totalTimeLogged": "Total time actually logged by team members across all tasks.",
|
||||
"estimatedCostHourly": "Estimated cost calculated as: Estimated Hours × Hourly Rates for assigned team members.",
|
||||
"estimatedCostManDays": "Estimated cost calculated as: Estimated Man Days × Daily Rates for assigned team members.",
|
||||
"actualCost": "Actual cost based on time logged. Calculated as: Time Logged × Hourly Rates for team members.",
|
||||
"fixedCost": "Fixed costs that don't depend on time spent. Added manually per task.",
|
||||
"totalBudgetHourly": "Total budgeted cost including estimated cost (Hours × Hourly Rates) + Fixed Costs.",
|
||||
"totalBudgetManDays": "Total budgeted cost including estimated cost (Man Days × Daily Rates) + Fixed Costs.",
|
||||
"totalActual": "Total actual cost including time-based cost + Fixed Costs.",
|
||||
"variance": "Cost variance: Total Budgeted Costs - Total Actual Cost. Positive values indicate under-budget, negative values indicate over-budget."
|
||||
"hours": "所有任务的总预估工时。根据任务时间估算计算。",
|
||||
"manDays": "所有任务的总预估人天。基于每个工作日 {{hoursPerDay}} 小时。",
|
||||
"actualManDays": "基于记录时间花费的实际人天。计算方式:总记录时间 ÷ {{hoursPerDay}} 小时/天。",
|
||||
"effortVariance": "预估人天与实际人天之间的差异。正值表示过度估算,负值表示估算不足。",
|
||||
"totalTimeLogged": "团队成员在所有任务中实际记录的总时间。",
|
||||
"estimatedCostHourly": "预估成本计算方式:预估工时 × 分配团队成员的小时费率。",
|
||||
"estimatedCostManDays": "预估成本计算方式:预估人天 × 分配团队成员的日费率。",
|
||||
"actualCost": "基于记录时间的实际成本。计算方式:记录时间 × 团队成员的小时费率。",
|
||||
"fixedCost": "不依赖时间花费的固定成本。每个任务手动添加。",
|
||||
"totalBudgetHourly": "总预算成本包括预估成本(工时 × 小时费率)+ 固定成本。",
|
||||
"totalBudgetManDays": "总预算成本包括预估成本(人天 × 日费率)+ 固定成本。",
|
||||
"totalActual": "总实际成本包括基于时间的成本 + 固定成本。",
|
||||
"variance": "成本偏差:总预算成本 - 总实际成本。正值表示预算内,负值表示超预算。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,5 +48,8 @@
|
||||
"jobTitleCreatedSuccess": "职位名称创建成功",
|
||||
"jobTitleCreateError": "职位名称创建失败",
|
||||
"createButton": "创建",
|
||||
"cancelButton": "取消"
|
||||
"cancelButton": "取消",
|
||||
"discardButton": "放弃",
|
||||
"manDaysCalculationMessage": "组织正在使用人日计算({{hours}}小时/天)。上述费率代表日费率。",
|
||||
"hourlyCalculationMessage": "组织正在使用小时计算。上述费率代表小时费率。"
|
||||
}
|
||||
|
||||
@@ -303,4 +303,9 @@ export const adminCenterApiService = {
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async getAdminCenterSettings(): Promise<IServerResponse<IOrganization>> {
|
||||
const response = await apiClient.get<IServerResponse<IOrganization>>(`${rootUrl}/settings`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -55,6 +55,8 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
||||
const [editModalVisible, setEditModalVisible] = useState(false);
|
||||
const [selectedHoliday, setSelectedHoliday] = useState<IHolidayCalendarEvent | null>(null);
|
||||
const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs());
|
||||
const [isPopulatingHolidays, setIsPopulatingHolidays] = useState(false);
|
||||
const [hasAttemptedPopulation, setHasAttemptedPopulation] = useState(false);
|
||||
|
||||
const fetchHolidayTypes = async () => {
|
||||
try {
|
||||
@@ -69,9 +71,18 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
||||
|
||||
const populateHolidaysIfNeeded = async () => {
|
||||
// Check if we have holiday settings with a country code but no holidays
|
||||
if (holidaySettings?.country_code && holidays.length === 0) {
|
||||
// Also check if we haven't already attempted population and we're not currently populating
|
||||
if (
|
||||
holidaySettings?.country_code &&
|
||||
holidays.length === 0 &&
|
||||
!hasAttemptedPopulation &&
|
||||
!isPopulatingHolidays
|
||||
) {
|
||||
try {
|
||||
console.log('🔄 No holidays found, attempting to populate official holidays...');
|
||||
setIsPopulatingHolidays(true);
|
||||
setHasAttemptedPopulation(true);
|
||||
|
||||
const populateRes = await holidayApiService.populateCountryHolidays();
|
||||
if (populateRes.done) {
|
||||
console.log('✅ Official holidays populated successfully');
|
||||
@@ -80,6 +91,8 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Could not populate official holidays:', error);
|
||||
} finally {
|
||||
setIsPopulatingHolidays(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -110,7 +123,12 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
||||
// Check if we need to populate holidays when holiday settings are loaded
|
||||
useEffect(() => {
|
||||
populateHolidaysIfNeeded();
|
||||
}, [holidaySettings, holidays.length]);
|
||||
}, [holidaySettings]);
|
||||
|
||||
// Reset population attempt state when holiday settings change
|
||||
useEffect(() => {
|
||||
setHasAttemptedPopulation(false);
|
||||
}, [holidaySettings?.country_code]);
|
||||
|
||||
const customHolidays = useMemo(() => {
|
||||
return holidays.filter(holiday => holiday.source === 'custom');
|
||||
@@ -300,6 +318,11 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
||||
{holidaySettings.country_code}
|
||||
{holidaySettings.state_code && ` (${holidaySettings.state_code})`}
|
||||
</span>
|
||||
{isPopulatingHolidays && (
|
||||
<span style={{ marginLeft: 8, color: '#faad14' }}>
|
||||
🔄 Populating official holidays...
|
||||
</span>
|
||||
)}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -400,7 +400,7 @@ const RateCardDrawer = ({
|
||||
handleDeleteRole(index);
|
||||
}}
|
||||
>
|
||||
<Tooltip title="Delete">
|
||||
<Tooltip title={t('deleteTooltip') || 'Delete'}>
|
||||
<Button size="small" icon={<DeleteOutlined />} />
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
@@ -504,10 +504,10 @@ const RateCardDrawer = ({
|
||||
action={
|
||||
<Space direction="horizontal">
|
||||
<Button size="small" type="primary" onClick={handleConfirmSave}>
|
||||
Save
|
||||
{t('saveButton') || 'Save'}
|
||||
</Button>
|
||||
<Button size="small" danger onClick={handleConfirmDiscard}>
|
||||
Discard
|
||||
{t('discardButton') || 'Discard'}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
@@ -568,8 +568,10 @@ const RateCardDrawer = ({
|
||||
<Alert
|
||||
message={
|
||||
isManDaysMethod
|
||||
? `Organization is using man days calculation (${organization.hours_per_day || 8}h/day). Rates above represent daily rates.`
|
||||
: 'Organization is using hourly calculation. Rates above represent hourly rates.'
|
||||
? t('manDaysCalculationMessage', {
|
||||
hours: organization.hours_per_day || 8
|
||||
}) || `Organization is using man days calculation (${organization.hours_per_day || 8}h/day). Rates above represent daily rates.`
|
||||
: t('hourlyCalculationMessage') || 'Organization is using hourly calculation. Rates above represent hourly rates.'
|
||||
}
|
||||
type="info"
|
||||
showIcon
|
||||
|
||||
@@ -83,6 +83,14 @@ export const fetchOrganizationDetails = createAsyncThunk(
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchAdminCenterSettings = createAsyncThunk(
|
||||
'adminCenter/fetchAdminCenterSettings',
|
||||
async () => {
|
||||
const res = await adminCenterApiService.getAdminCenterSettings();
|
||||
return res.body;
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchOrganizationAdmins = createAsyncThunk(
|
||||
'adminCenter/fetchOrganizationAdmins',
|
||||
async () => {
|
||||
@@ -207,6 +215,17 @@ const adminCenterSlice = createSlice({
|
||||
state.loadingOrganization = false;
|
||||
});
|
||||
|
||||
builder.addCase(fetchAdminCenterSettings.pending, (state, action) => {
|
||||
state.loadingOrganization = true;
|
||||
});
|
||||
builder.addCase(fetchAdminCenterSettings.fulfilled, (state, action) => {
|
||||
state.organization = action.payload;
|
||||
state.loadingOrganization = false;
|
||||
});
|
||||
builder.addCase(fetchAdminCenterSettings.rejected, (state, action) => {
|
||||
state.loadingOrganization = false;
|
||||
});
|
||||
|
||||
builder.addCase(fetchOrganizationAdmins.pending, (state, action) => {
|
||||
state.loadingOrganizationAdmins = true;
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import { PageHeader } from '@ant-design/pro-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import logger from '@/utils/errorLogger';
|
||||
import { scheduleAPIService } from '@/api/schedule/schedule.api.service';
|
||||
import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service';
|
||||
import { Settings } from '@/types/schedule/schedule-v2.types';
|
||||
import OrganizationCalculationMethod from '@/components/admin-center/overview/organization-calculation-method/organization-calculation-method';
|
||||
import HolidayCalendar from '@/components/admin-center/overview/holiday-calendar/holiday-calendar';
|
||||
@@ -25,6 +26,7 @@ import { useAppSelector } from '@/hooks/useAppSelector';
|
||||
import { RootState } from '@/app/store';
|
||||
import {
|
||||
fetchOrganizationDetails,
|
||||
fetchAdminCenterSettings,
|
||||
fetchOrganizationAdmins,
|
||||
fetchHolidaySettings,
|
||||
updateHolidaySettings,
|
||||
@@ -49,11 +51,11 @@ const SettingsPage: React.FC = () => {
|
||||
|
||||
const { t } = useTranslation('admin-center/settings');
|
||||
|
||||
const getOrganizationDetails = async () => {
|
||||
const getAdminCenterSettings = async () => {
|
||||
try {
|
||||
await dispatch(fetchOrganizationDetails()).unwrap();
|
||||
await dispatch(fetchAdminCenterSettings()).unwrap();
|
||||
} catch (error) {
|
||||
logger.error('Error getting organization details', error);
|
||||
logger.error('Error getting admin center settings', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,7 +113,7 @@ const SettingsPage: React.FC = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getOrganizationDetails();
|
||||
getAdminCenterSettings();
|
||||
getOrganizationAdmins();
|
||||
getOrgWorkingSettings();
|
||||
dispatch(fetchHolidaySettings());
|
||||
@@ -202,10 +204,12 @@ const SettingsPage: React.FC = () => {
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
<OrganizationCalculationMethod
|
||||
organization={organization}
|
||||
refetch={getOrganizationDetails}
|
||||
/>
|
||||
{organization && (
|
||||
<OrganizationCalculationMethod
|
||||
organization={organization}
|
||||
refetch={getAdminCenterSettings}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||
|
||||
Reference in New Issue
Block a user