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:
@@ -71,6 +71,27 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
contact_number,
|
contact_number,
|
||||||
contact_number_secondary,
|
contact_number_secondary,
|
||||||
(SELECT email FROM users WHERE id = organizations.user_id),
|
(SELECT email FROM users WHERE id = organizations.user_id),
|
||||||
|
(SELECT name FROM users WHERE id = organizations.user_id) AS owner_name,
|
||||||
|
calculation_method,
|
||||||
|
hours_per_day
|
||||||
|
FROM organizations
|
||||||
|
WHERE user_id = $1;`;
|
||||||
|
const result = await db.query(q, [req.user?.owner_id]);
|
||||||
|
const [data] = result.rows;
|
||||||
|
return res.status(200).send(new ServerResponse(true, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
@HandleExceptions()
|
||||||
|
public static async getAdminCenterSettings(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const q = `SELECT organization_name AS name,
|
||||||
|
contact_number,
|
||||||
|
contact_number_secondary,
|
||||||
|
calculation_method,
|
||||||
|
hours_per_day,
|
||||||
|
(SELECT email FROM users WHERE id = organizations.user_id),
|
||||||
(SELECT name FROM users WHERE id = organizations.user_id) AS owner_name
|
(SELECT name FROM users WHERE id = organizations.user_id) AS owner_name
|
||||||
FROM organizations
|
FROM organizations
|
||||||
WHERE user_id = $1;`;
|
WHERE user_id = $1;`;
|
||||||
|
|||||||
@@ -264,6 +264,27 @@ export default class HolidayController extends WorklenzControllerBase {
|
|||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
public static async populateCountryHolidays(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
public static async populateCountryHolidays(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
||||||
|
// Check if this organization has recently populated holidays (within last hour)
|
||||||
|
const recentPopulationCheck = `
|
||||||
|
SELECT COUNT(*) as count
|
||||||
|
FROM organization_holidays
|
||||||
|
WHERE organization_id = (SELECT id FROM organizations WHERE user_id = $1)
|
||||||
|
AND created_at > NOW() - INTERVAL '1 hour'
|
||||||
|
`;
|
||||||
|
|
||||||
|
const recentResult = await db.query(recentPopulationCheck, [req.user?.owner_id]);
|
||||||
|
const recentCount = parseInt(recentResult.rows[0]?.count || '0');
|
||||||
|
|
||||||
|
// If there are recent holidays added, skip population
|
||||||
|
if (recentCount > 10) {
|
||||||
|
return res.status(200).send(new ServerResponse(true, {
|
||||||
|
success: true,
|
||||||
|
message: "Holidays were recently populated, skipping to avoid duplicates",
|
||||||
|
total_populated: 0,
|
||||||
|
recently_populated: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
const Holidays = require("date-holidays");
|
const Holidays = require("date-holidays");
|
||||||
|
|
||||||
const countries = [
|
const countries = [
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import teamOwnerOrAdminValidator from "../../middlewares/validators/team-owner-o
|
|||||||
const adminCenterApiRouter = express.Router();
|
const adminCenterApiRouter = express.Router();
|
||||||
|
|
||||||
// overview
|
// overview
|
||||||
|
adminCenterApiRouter.get("/settings", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getAdminCenterSettings));
|
||||||
adminCenterApiRouter.get("/organization", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationDetails));
|
adminCenterApiRouter.get("/organization", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationDetails));
|
||||||
adminCenterApiRouter.get("/organization/admins", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationAdmins));
|
adminCenterApiRouter.get("/organization/admins", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationAdmins));
|
||||||
adminCenterApiRouter.put("/organization", teamOwnerOrAdminValidator, organizationSettingsValidator, safeControllerFunction(AdminCenterController.updateOrganizationName));
|
adminCenterApiRouter.put("/organization", teamOwnerOrAdminValidator, organizationSettingsValidator, safeControllerFunction(AdminCenterController.updateOrganizationName));
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
"saveButton": "Save",
|
"saveButton": "Save",
|
||||||
"jobTitleColumn": "Job Title",
|
"jobTitleColumn": "Job Title",
|
||||||
"ratePerHourColumn": "Rate per hour",
|
"ratePerHourColumn": "Rate per hour",
|
||||||
"ratePerManDayColumn": "Rate per man day",
|
"ratePerManDayColumn": "Tarifa për ditë-njeri",
|
||||||
"calculationMethodText": "Calculation Method",
|
"calculationMethodText": "Calculation Method",
|
||||||
"hourlyRatesText": "Hourly Rates",
|
"hourlyRatesText": "Hourly Rates",
|
||||||
"manDaysText": "Man Days",
|
"manDaysText": "Man Days",
|
||||||
|
|||||||
@@ -48,5 +48,8 @@
|
|||||||
"jobTitleCreatedSuccess": "Titulli i punës u krijua me sukses",
|
"jobTitleCreatedSuccess": "Titulli i punës u krijua me sukses",
|
||||||
"jobTitleCreateError": "Dështoi të krijohet titulli i punës",
|
"jobTitleCreateError": "Dështoi të krijohet titulli i punës",
|
||||||
"createButton": "Krijo",
|
"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",
|
"saveButton": "Save",
|
||||||
"jobTitleColumn": "Job Title",
|
"jobTitleColumn": "Job Title",
|
||||||
"ratePerHourColumn": "Rate per hour",
|
"ratePerHourColumn": "Rate per hour",
|
||||||
"ratePerManDayColumn": "Rate per man day",
|
"ratePerManDayColumn": "Satz pro Manntag",
|
||||||
"calculationMethodText": "Calculation Method",
|
"calculationMethodText": "Calculation Method",
|
||||||
"hourlyRatesText": "Hourly Rates",
|
"hourlyRatesText": "Hourly Rates",
|
||||||
"manDaysText": "Man Days",
|
"manDaysText": "Man Days",
|
||||||
|
|||||||
@@ -48,5 +48,8 @@
|
|||||||
"jobTitleCreatedSuccess": "Berufsbezeichnung erfolgreich erstellt",
|
"jobTitleCreatedSuccess": "Berufsbezeichnung erfolgreich erstellt",
|
||||||
"jobTitleCreateError": "Berufsbezeichnung konnte nicht erstellt werden",
|
"jobTitleCreateError": "Berufsbezeichnung konnte nicht erstellt werden",
|
||||||
"createButton": "Erstellen",
|
"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",
|
"jobTitleCreatedSuccess": "Job title created successfully",
|
||||||
"jobTitleCreateError": "Failed to create job title",
|
"jobTitleCreateError": "Failed to create job title",
|
||||||
"createButton": "Create",
|
"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",
|
"saveButton": "Save",
|
||||||
"jobTitleColumn": "Job Title",
|
"jobTitleColumn": "Job Title",
|
||||||
"ratePerHourColumn": "Rate per hour",
|
"ratePerHourColumn": "Rate per hour",
|
||||||
"ratePerManDayColumn": "Rate per man day",
|
"ratePerManDayColumn": "Tarifa por día-hombre",
|
||||||
"calculationMethodText": "Calculation Method",
|
"calculationMethodText": "Calculation Method",
|
||||||
"hourlyRatesText": "Hourly Rates",
|
"hourlyRatesText": "Hourly Rates",
|
||||||
"manDaysText": "Man Days",
|
"manDaysText": "Man Days",
|
||||||
|
|||||||
@@ -48,5 +48,8 @@
|
|||||||
"jobTitleCreatedSuccess": "Título de trabajo creado con éxito",
|
"jobTitleCreatedSuccess": "Título de trabajo creado con éxito",
|
||||||
"jobTitleCreateError": "No se pudo crear el título de trabajo",
|
"jobTitleCreateError": "No se pudo crear el título de trabajo",
|
||||||
"createButton": "Crear",
|
"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",
|
"financeText": "Finanças",
|
||||||
"ratecardSingularText": "Rate Card",
|
"ratecardSingularText": "Tabela de Preços",
|
||||||
"groupByText": "Group by",
|
"groupByText": "Agrupar por",
|
||||||
"statusText": "Status",
|
"statusText": "Status",
|
||||||
"phaseText": "Phase",
|
"phaseText": "Fase",
|
||||||
"priorityText": "Priority",
|
"priorityText": "Prioridade",
|
||||||
"exportButton": "Export",
|
"exportButton": "Exportar",
|
||||||
"currencyText": "Currency",
|
"currencyText": "Moeda",
|
||||||
"importButton": "Import",
|
"importButton": "Importar",
|
||||||
"filterText": "Filter",
|
"filterText": "Filtrar",
|
||||||
"billableOnlyText": "Billable Only",
|
"billableOnlyText": "Apenas Faturável",
|
||||||
"nonBillableOnlyText": "Non-Billable Only",
|
"nonBillableOnlyText": "Apenas Não Faturável",
|
||||||
"allTasksText": "All Tasks",
|
"allTasksText": "Todas as Tarefas",
|
||||||
"projectBudgetOverviewText": "Project Budget Overview",
|
"projectBudgetOverviewText": "Visão Geral do Orçamento do Projeto",
|
||||||
"taskColumn": "Task",
|
"taskColumn": "Tarefa",
|
||||||
"membersColumn": "Members",
|
"membersColumn": "Membros",
|
||||||
"hoursColumn": "Estimated Hours",
|
"hoursColumn": "Horas Estimadas",
|
||||||
"manDaysColumn": "Estimated Man Days",
|
"manDaysColumn": "Dias-Homem Estimados",
|
||||||
"actualManDaysColumn": "Actual Man Days",
|
"actualManDaysColumn": "Dias-Homem Reais",
|
||||||
"effortVarianceColumn": "Effort Variance",
|
"effortVarianceColumn": "Variação do Esforço",
|
||||||
"totalTimeLoggedColumn": "Total Time Logged",
|
"totalTimeLoggedColumn": "Tempo Total Registrado",
|
||||||
"costColumn": "Actual Cost",
|
"costColumn": "Custo Real",
|
||||||
"estimatedCostColumn": "Estimated Cost",
|
"estimatedCostColumn": "Custo Estimado",
|
||||||
"fixedCostColumn": "Fixed Cost",
|
"fixedCostColumn": "Custo Fixo",
|
||||||
"totalBudgetedCostColumn": "Total Budgeted Cost",
|
"totalBudgetedCostColumn": "Custo Total Orçado",
|
||||||
"totalActualCostColumn": "Total Actual Cost",
|
"totalActualCostColumn": "Custo Total Real",
|
||||||
"varianceColumn": "Variance",
|
"varianceColumn": "Variação",
|
||||||
"totalText": "Total",
|
"totalText": "Total",
|
||||||
"noTasksFound": "No tasks found",
|
"noTasksFound": "Nenhuma tarefa encontrada",
|
||||||
"addRoleButton": "+ Add Role",
|
"addRoleButton": "+ Adicionar Função",
|
||||||
"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.",
|
"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": "Save",
|
"saveButton": "Salvar",
|
||||||
"jobTitleColumn": "Job Title",
|
"jobTitleColumn": "Cargo",
|
||||||
"ratePerHourColumn": "Rate per hour",
|
"ratePerHourColumn": "Taxa por hora",
|
||||||
"ratePerManDayColumn": "Rate per man day",
|
"ratePerManDayColumn": "Taxa por dia-homem",
|
||||||
"calculationMethodText": "Calculation Method",
|
"calculationMethodText": "Método de Cálculo",
|
||||||
"hourlyRatesText": "Hourly Rates",
|
"hourlyRatesText": "Taxas por Hora",
|
||||||
"manDaysText": "Man Days",
|
"manDaysText": "Dias-Homem",
|
||||||
"hoursPerDayText": "Hours per Day",
|
"hoursPerDayText": "Horas por Dia",
|
||||||
"ratecardPluralText": "Rate Cards",
|
"ratecardPluralText": "Tabelas de Preços",
|
||||||
"labourHoursColumn": "Labour Hours",
|
"labourHoursColumn": "Horas de Trabalho",
|
||||||
"actions": "Actions",
|
"actions": "Ações",
|
||||||
"selectJobTitle": "Select Job Title",
|
"selectJobTitle": "Selecionar Cargo",
|
||||||
"ratecardsPluralText": "Rate Card Templates",
|
"ratecardsPluralText": "Modelos de Tabela de Preços",
|
||||||
"deleteConfirm": "Are you sure ?",
|
"deleteConfirm": "Tem certeza?",
|
||||||
"yes": "Yes",
|
"yes": "Sim",
|
||||||
"no": "No",
|
"no": "Não",
|
||||||
"alreadyImportedRateCardMessage": "A rate card has already been imported. Clear all imported rate cards to add a new one.",
|
"alreadyImportedRateCardMessage": "Uma tabela de preços já foi importada. Limpe todas as tabelas de preços importadas para adicionar uma nova.",
|
||||||
"budgetOverviewTooltips": {
|
"budgetOverviewTooltips": {
|
||||||
"manualBudget": "Manual project budget amount set by project manager",
|
"manualBudget": "Valor do orçamento manual do projeto definido pelo gerente do projeto",
|
||||||
"totalActualCost": "Total actual cost including fixed costs",
|
"totalActualCost": "Custo total real incluindo custos fixos",
|
||||||
"variance": "Difference between manual budget and actual cost",
|
"variance": "Diferença entre orçamento manual e custo real",
|
||||||
"utilization": "Percentage of manual budget utilized",
|
"utilization": "Porcentagem do orçamento manual utilizado",
|
||||||
"estimatedHours": "Total estimated hours from all tasks",
|
"estimatedHours": "Total de horas estimadas de todas as tarefas",
|
||||||
"fixedCosts": "Total fixed costs from all tasks",
|
"fixedCosts": "Total de custos fixos de todas as tarefas",
|
||||||
"timeBasedCost": "Actual cost from time tracking (excluding fixed costs)",
|
"timeBasedCost": "Custo real do rastreamento de tempo (excluindo custos fixos)",
|
||||||
"remainingBudget": "Remaining budget amount"
|
"remainingBudget": "Valor do orçamento restante"
|
||||||
},
|
},
|
||||||
"budgetModal": {
|
"budgetModal": {
|
||||||
"title": "Edit Project Budget",
|
"title": "Editar Orçamento do Projeto",
|
||||||
"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.",
|
"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": "Enter budget amount",
|
"placeholder": "Digite o valor do orçamento",
|
||||||
"saveButton": "Save",
|
"saveButton": "Salvar",
|
||||||
"cancelButton": "Cancel"
|
"cancelButton": "Cancelar"
|
||||||
},
|
},
|
||||||
"budgetStatistics": {
|
"budgetStatistics": {
|
||||||
"manualBudget": "Manual Budget",
|
"manualBudget": "Orçamento Manual",
|
||||||
"totalActualCost": "Total Actual Cost",
|
"totalActualCost": "Custo Total Real",
|
||||||
"variance": "Variance",
|
"variance": "Variação",
|
||||||
"budgetUtilization": "Budget Utilization",
|
"budgetUtilization": "Utilização do Orçamento",
|
||||||
"estimatedHours": "Estimated Hours",
|
"estimatedHours": "Horas Estimadas",
|
||||||
"fixedCosts": "Fixed Costs",
|
"fixedCosts": "Custos Fixos",
|
||||||
"timeBasedCost": "Time-based Cost",
|
"timeBasedCost": "Custo Baseado em Tempo",
|
||||||
"remainingBudget": "Remaining Budget",
|
"remainingBudget": "Orçamento Restante",
|
||||||
"noManualBudgetSet": "(No Manual Budget Set)"
|
"noManualBudgetSet": "(Nenhum Orçamento Manual Definido)"
|
||||||
},
|
},
|
||||||
"budgetSettingsDrawer": {
|
"budgetSettingsDrawer": {
|
||||||
"title": "Project Budget Settings",
|
"title": "Configurações de Orçamento do Projeto",
|
||||||
"budgetConfiguration": "Budget Configuration",
|
"budgetConfiguration": "Configuração do Orçamento",
|
||||||
"projectBudget": "Project Budget",
|
"projectBudget": "Orçamento do Projeto",
|
||||||
"projectBudgetTooltip": "Total budget allocated for this project",
|
"projectBudgetTooltip": "Orçamento total alocado para este projeto",
|
||||||
"currency": "Currency",
|
"currency": "Moeda",
|
||||||
"costCalculationMethod": "Cost Calculation Method",
|
"costCalculationMethod": "Método de Cálculo de Custo",
|
||||||
"calculationMethod": "Calculation Method",
|
"calculationMethod": "Método de Cálculo",
|
||||||
"workingHoursPerDay": "Working Hours per Day",
|
"workingHoursPerDay": "Horas de Trabalho por Dia",
|
||||||
"workingHoursPerDayTooltip": "Number of working hours in a day for man-day calculations",
|
"workingHoursPerDayTooltip": "Número de horas de trabalho em um dia para cálculos de dia-homem",
|
||||||
"hourlyCalculationInfo": "Costs will be calculated using estimated hours × hourly rates",
|
"hourlyCalculationInfo": "Os custos serão calculados usando horas estimadas × taxas por hora",
|
||||||
"manDaysCalculationInfo": "Costs will be calculated using estimated man days × daily rates",
|
"manDaysCalculationInfo": "Os custos serão calculados usando dias-homem estimados × taxas diárias",
|
||||||
"importantNotes": "Important Notes",
|
"importantNotes": "Notas Importantes",
|
||||||
"calculationMethodChangeNote": "• Changing the calculation method will affect how costs are calculated for all tasks in this project",
|
"calculationMethodChangeNote": "• Alterar o método de cálculo afetará como os custos são calculados para todas as tarefas neste projeto",
|
||||||
"immediateEffectNote": "• Changes take effect immediately and will recalculate all project totals",
|
"immediateEffectNote": "• As alterações entram em vigor imediatamente e recalcularão todos os totais do projeto",
|
||||||
"projectWideNote": "• Budget settings apply to the entire project and all its tasks",
|
"projectWideNote": "• As configurações de orçamento se aplicam a todo o projeto e todas as suas tarefas",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancelar",
|
||||||
"saveChanges": "Save Changes",
|
"saveChanges": "Salvar Alterações",
|
||||||
"budgetSettingsUpdated": "Budget settings updated successfully",
|
"budgetSettingsUpdated": "Configurações de orçamento atualizadas com sucesso",
|
||||||
"budgetSettingsUpdateFailed": "Failed to update budget settings"
|
"budgetSettingsUpdateFailed": "Falha ao atualizar configurações de orçamento"
|
||||||
},
|
},
|
||||||
"columnTooltips": {
|
"columnTooltips": {
|
||||||
"hours": "Total estimated hours for all tasks. Calculated from task time estimates.",
|
"hours": "Total de horas estimadas para todas as tarefas. Calculado a partir das estimativas de tempo das tarefas.",
|
||||||
"manDays": "Total estimated man days for all tasks. Based on {{hoursPerDay}} hours per working day.",
|
"manDays": "Total de dias-homem estimados para todas as tarefas. Baseado em {{hoursPerDay}} horas por dia de trabalho.",
|
||||||
"actualManDays": "Actual man days spent based on time logged. Calculated as: Total Time Logged ÷ {{hoursPerDay}} hours per day.",
|
"actualManDays": "Dias-homem reais gastos com base no tempo registrado. Calculado como: Tempo Total Registrado ÷ {{hoursPerDay}} horas por dia.",
|
||||||
"effortVariance": "Difference between estimated and actual man days. Positive values indicate over-estimation, negative values indicate under-estimation.",
|
"effortVariance": "Diferença entre dias-homem estimados e reais. Valores positivos indicam superestimação, valores negativos indicam subestimação.",
|
||||||
"totalTimeLogged": "Total time actually logged by team members across all tasks.",
|
"totalTimeLogged": "Tempo total realmente registrado pelos membros da equipe em todas as tarefas.",
|
||||||
"estimatedCostHourly": "Estimated cost calculated as: Estimated Hours × Hourly Rates for assigned team members.",
|
"estimatedCostHourly": "Custo estimado calculado como: Horas Estimadas × Taxas por Hora para membros da equipe designados.",
|
||||||
"estimatedCostManDays": "Estimated cost calculated as: Estimated Man Days × Daily Rates for assigned team members.",
|
"estimatedCostManDays": "Custo estimado calculado como: Dias-Homem Estimados × Taxas Diárias para membros da equipe designados.",
|
||||||
"actualCost": "Actual cost based on time logged. Calculated as: Time Logged × Hourly Rates for team members.",
|
"actualCost": "Custo real baseado no tempo registrado. Calculado como: Tempo Registrado × Taxas por Hora para membros da equipe.",
|
||||||
"fixedCost": "Fixed costs that don't depend on time spent. Added manually per task.",
|
"fixedCost": "Custos fixos que não dependem do tempo gasto. Adicionados manualmente por tarefa.",
|
||||||
"totalBudgetHourly": "Total budgeted cost including estimated cost (Hours × Hourly Rates) + Fixed Costs.",
|
"totalBudgetHourly": "Custo total orçado incluindo custo estimado (Horas × Taxas por Hora) + Custos Fixos.",
|
||||||
"totalBudgetManDays": "Total budgeted cost including estimated cost (Man Days × Daily Rates) + Fixed Costs.",
|
"totalBudgetManDays": "Custo total orçado incluindo custo estimado (Dias-Homem × Taxas Diárias) + Custos Fixos.",
|
||||||
"totalActual": "Total actual cost including time-based cost + Fixed Costs.",
|
"totalActual": "Custo total real incluindo custo baseado em tempo + Custos Fixos.",
|
||||||
"variance": "Cost variance: Total Budgeted Costs - Total Actual Cost. Positive values indicate under-budget, negative values indicate over-budget."
|
"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",
|
"jobTitleCreatedSuccess": "Cargo criado com sucesso",
|
||||||
"jobTitleCreateError": "Falha ao criar cargo",
|
"jobTitleCreateError": "Falha ao criar cargo",
|
||||||
"createButton": "Criar",
|
"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",
|
"financeText": "财务",
|
||||||
"ratecardSingularText": "Rate Card",
|
"ratecardSingularText": "费率卡",
|
||||||
"groupByText": "Group by",
|
"groupByText": "分组方式",
|
||||||
"statusText": "Status",
|
"statusText": "状态",
|
||||||
"phaseText": "Phase",
|
"phaseText": "阶段",
|
||||||
"priorityText": "Priority",
|
"priorityText": "优先级",
|
||||||
"exportButton": "Export",
|
"exportButton": "导出",
|
||||||
"currencyText": "Currency",
|
"currencyText": "货币",
|
||||||
"importButton": "Import",
|
"importButton": "导入",
|
||||||
"filterText": "Filter",
|
"filterText": "筛选",
|
||||||
"billableOnlyText": "Billable Only",
|
"billableOnlyText": "仅可计费",
|
||||||
"nonBillableOnlyText": "Non-Billable Only",
|
"nonBillableOnlyText": "仅不可计费",
|
||||||
"allTasksText": "All Tasks",
|
"allTasksText": "所有任务",
|
||||||
"projectBudgetOverviewText": "Project Budget Overview",
|
"projectBudgetOverviewText": "项目预算概览",
|
||||||
"taskColumn": "Task",
|
"taskColumn": "任务",
|
||||||
"membersColumn": "Members",
|
"membersColumn": "成员",
|
||||||
"hoursColumn": "Estimated Hours",
|
"hoursColumn": "预估工时",
|
||||||
"manDaysColumn": "Estimated Man Days",
|
"manDaysColumn": "预估人天",
|
||||||
"actualManDaysColumn": "Actual Man Days",
|
"actualManDaysColumn": "实际人天",
|
||||||
"effortVarianceColumn": "Effort Variance",
|
"effortVarianceColumn": "工作量偏差",
|
||||||
"totalTimeLoggedColumn": "Total Time Logged",
|
"totalTimeLoggedColumn": "总记录时间",
|
||||||
"costColumn": "Actual Cost",
|
"costColumn": "实际成本",
|
||||||
"estimatedCostColumn": "Estimated Cost",
|
"estimatedCostColumn": "预估成本",
|
||||||
"fixedCostColumn": "Fixed Cost",
|
"fixedCostColumn": "固定成本",
|
||||||
"totalBudgetedCostColumn": "Total Budgeted Cost",
|
"totalBudgetedCostColumn": "总预算成本",
|
||||||
"totalActualCostColumn": "Total Actual Cost",
|
"totalActualCostColumn": "总实际成本",
|
||||||
"varianceColumn": "Variance",
|
"varianceColumn": "偏差",
|
||||||
"totalText": "Total",
|
"totalText": "总计",
|
||||||
"noTasksFound": "No tasks found",
|
"noTasksFound": "未找到任务",
|
||||||
"addRoleButton": "+ Add Role",
|
"addRoleButton": "+ 添加角色",
|
||||||
"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.",
|
"ratecardImportantNotice": "* 此费率卡基于公司标准职位和费率生成。但是,您可以根据项目灵活修改。这些更改不会影响组织的标准职位和费率。",
|
||||||
"saveButton": "Save",
|
"saveButton": "保存",
|
||||||
"jobTitleColumn": "Job Title",
|
"jobTitleColumn": "职位",
|
||||||
"ratePerHourColumn": "Rate per hour",
|
"ratePerHourColumn": "每小时费率",
|
||||||
"ratePerManDayColumn": "Rate per man day",
|
"ratePerManDayColumn": "每人每日费率",
|
||||||
"calculationMethodText": "Calculation Method",
|
"calculationMethodText": "计算方法",
|
||||||
"hourlyRatesText": "Hourly Rates",
|
"hourlyRatesText": "小时费率",
|
||||||
"manDaysText": "Man Days",
|
"manDaysText": "人天",
|
||||||
"hoursPerDayText": "Hours per Day",
|
"hoursPerDayText": "每日工时",
|
||||||
"ratecardPluralText": "Rate Cards",
|
"ratecardPluralText": "费率卡",
|
||||||
"labourHoursColumn": "Labour Hours",
|
"labourHoursColumn": "工时",
|
||||||
"actions": "Actions",
|
"actions": "操作",
|
||||||
"selectJobTitle": "Select Job Title",
|
"selectJobTitle": "选择职位",
|
||||||
"ratecardsPluralText": "Rate Card Templates",
|
"ratecardsPluralText": "费率卡模板",
|
||||||
"deleteConfirm": "Are you sure ?",
|
"deleteConfirm": "您确定吗?",
|
||||||
"yes": "Yes",
|
"yes": "是",
|
||||||
"no": "No",
|
"no": "否",
|
||||||
"alreadyImportedRateCardMessage": "A rate card has already been imported. Clear all imported rate cards to add a new one.",
|
"alreadyImportedRateCardMessage": "已导入费率卡。清除所有已导入的费率卡以添加新的费率卡。",
|
||||||
"budgetOverviewTooltips": {
|
"budgetOverviewTooltips": {
|
||||||
"manualBudget": "Manual project budget amount set by project manager",
|
"manualBudget": "项目经理设置的手动项目预算金额",
|
||||||
"totalActualCost": "Total actual cost including fixed costs",
|
"totalActualCost": "包括固定成本在内的总实际成本",
|
||||||
"variance": "Difference between manual budget and actual cost",
|
"variance": "手动预算与实际成本之间的差异",
|
||||||
"utilization": "Percentage of manual budget utilized",
|
"utilization": "手动预算使用百分比",
|
||||||
"estimatedHours": "Total estimated hours from all tasks",
|
"estimatedHours": "所有任务的总预估工时",
|
||||||
"fixedCosts": "Total fixed costs from all tasks",
|
"fixedCosts": "所有任务的总固定成本",
|
||||||
"timeBasedCost": "Actual cost from time tracking (excluding fixed costs)",
|
"timeBasedCost": "基于时间跟踪的实际成本(不包括固定成本)",
|
||||||
"remainingBudget": "Remaining budget amount"
|
"remainingBudget": "剩余预算金额"
|
||||||
},
|
},
|
||||||
"budgetModal": {
|
"budgetModal": {
|
||||||
"title": "Edit Project Budget",
|
"title": "编辑项目预算",
|
||||||
"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.",
|
"description": "为此项目设置手动预算。此预算将用于所有财务计算,应包括基于时间的成本和固定成本。",
|
||||||
"placeholder": "Enter budget amount",
|
"placeholder": "输入预算金额",
|
||||||
"saveButton": "Save",
|
"saveButton": "保存",
|
||||||
"cancelButton": "Cancel"
|
"cancelButton": "取消"
|
||||||
},
|
},
|
||||||
"budgetStatistics": {
|
"budgetStatistics": {
|
||||||
"manualBudget": "Manual Budget",
|
"manualBudget": "手动预算",
|
||||||
"totalActualCost": "Total Actual Cost",
|
"totalActualCost": "总实际成本",
|
||||||
"variance": "Variance",
|
"variance": "偏差",
|
||||||
"budgetUtilization": "Budget Utilization",
|
"budgetUtilization": "预算使用率",
|
||||||
"estimatedHours": "Estimated Hours",
|
"estimatedHours": "预估工时",
|
||||||
"fixedCosts": "Fixed Costs",
|
"fixedCosts": "固定成本",
|
||||||
"timeBasedCost": "Time-based Cost",
|
"timeBasedCost": "基于时间的成本",
|
||||||
"remainingBudget": "Remaining Budget",
|
"remainingBudget": "剩余预算",
|
||||||
"noManualBudgetSet": "(No Manual Budget Set)"
|
"noManualBudgetSet": "(未设置手动预算)"
|
||||||
},
|
},
|
||||||
"budgetSettingsDrawer": {
|
"budgetSettingsDrawer": {
|
||||||
"title": "Project Budget Settings",
|
"title": "项目预算设置",
|
||||||
"budgetConfiguration": "Budget Configuration",
|
"budgetConfiguration": "预算配置",
|
||||||
"projectBudget": "Project Budget",
|
"projectBudget": "项目预算",
|
||||||
"projectBudgetTooltip": "Total budget allocated for this project",
|
"projectBudgetTooltip": "为此项目分配的总预算",
|
||||||
"currency": "Currency",
|
"currency": "货币",
|
||||||
"costCalculationMethod": "Cost Calculation Method",
|
"costCalculationMethod": "成本计算方法",
|
||||||
"calculationMethod": "Calculation Method",
|
"calculationMethod": "计算方法",
|
||||||
"workingHoursPerDay": "Working Hours per Day",
|
"workingHoursPerDay": "每日工作工时",
|
||||||
"workingHoursPerDayTooltip": "Number of working hours in a day for man-day calculations",
|
"workingHoursPerDayTooltip": "人天计算中每日的工作工时数",
|
||||||
"hourlyCalculationInfo": "Costs will be calculated using estimated hours × hourly rates",
|
"hourlyCalculationInfo": "成本将使用预估工时 × 小时费率计算",
|
||||||
"manDaysCalculationInfo": "Costs will be calculated using estimated man days × daily rates",
|
"manDaysCalculationInfo": "成本将使用预估人天 × 日费率计算",
|
||||||
"importantNotes": "Important Notes",
|
"importantNotes": "重要说明",
|
||||||
"calculationMethodChangeNote": "• Changing the calculation method will affect how costs are calculated for all tasks in this project",
|
"calculationMethodChangeNote": "• 更改计算方法将影响此项目中所有任务的成本计算方式",
|
||||||
"immediateEffectNote": "• Changes take effect immediately and will recalculate all project totals",
|
"immediateEffectNote": "• 更改立即生效并将重新计算所有项目总计",
|
||||||
"projectWideNote": "• Budget settings apply to the entire project and all its tasks",
|
"projectWideNote": "• 预算设置适用于整个项目及其所有任务",
|
||||||
"cancel": "Cancel",
|
"cancel": "取消",
|
||||||
"saveChanges": "Save Changes",
|
"saveChanges": "保存更改",
|
||||||
"budgetSettingsUpdated": "Budget settings updated successfully",
|
"budgetSettingsUpdated": "预算设置更新成功",
|
||||||
"budgetSettingsUpdateFailed": "Failed to update budget settings"
|
"budgetSettingsUpdateFailed": "更新预算设置失败"
|
||||||
},
|
},
|
||||||
"columnTooltips": {
|
"columnTooltips": {
|
||||||
"hours": "Total estimated hours for all tasks. Calculated from task time estimates.",
|
"hours": "所有任务的总预估工时。根据任务时间估算计算。",
|
||||||
"manDays": "Total estimated man days for all tasks. Based on {{hoursPerDay}} hours per working day.",
|
"manDays": "所有任务的总预估人天。基于每个工作日 {{hoursPerDay}} 小时。",
|
||||||
"actualManDays": "Actual man days spent based on time logged. Calculated as: Total Time Logged ÷ {{hoursPerDay}} hours per day.",
|
"actualManDays": "基于记录时间花费的实际人天。计算方式:总记录时间 ÷ {{hoursPerDay}} 小时/天。",
|
||||||
"effortVariance": "Difference between estimated and actual man days. Positive values indicate over-estimation, negative values indicate under-estimation.",
|
"effortVariance": "预估人天与实际人天之间的差异。正值表示过度估算,负值表示估算不足。",
|
||||||
"totalTimeLogged": "Total time actually logged by team members across all tasks.",
|
"totalTimeLogged": "团队成员在所有任务中实际记录的总时间。",
|
||||||
"estimatedCostHourly": "Estimated cost calculated as: Estimated Hours × Hourly Rates for assigned team members.",
|
"estimatedCostHourly": "预估成本计算方式:预估工时 × 分配团队成员的小时费率。",
|
||||||
"estimatedCostManDays": "Estimated cost calculated as: Estimated Man Days × Daily Rates for assigned team members.",
|
"estimatedCostManDays": "预估成本计算方式:预估人天 × 分配团队成员的日费率。",
|
||||||
"actualCost": "Actual cost based on time logged. Calculated as: Time Logged × Hourly Rates for team members.",
|
"actualCost": "基于记录时间的实际成本。计算方式:记录时间 × 团队成员的小时费率。",
|
||||||
"fixedCost": "Fixed costs that don't depend on time spent. Added manually per task.",
|
"fixedCost": "不依赖时间花费的固定成本。每个任务手动添加。",
|
||||||
"totalBudgetHourly": "Total budgeted cost including estimated cost (Hours × Hourly Rates) + Fixed Costs.",
|
"totalBudgetHourly": "总预算成本包括预估成本(工时 × 小时费率)+ 固定成本。",
|
||||||
"totalBudgetManDays": "Total budgeted cost including estimated cost (Man Days × Daily Rates) + Fixed Costs.",
|
"totalBudgetManDays": "总预算成本包括预估成本(人天 × 日费率)+ 固定成本。",
|
||||||
"totalActual": "Total actual cost including time-based cost + Fixed Costs.",
|
"totalActual": "总实际成本包括基于时间的成本 + 固定成本。",
|
||||||
"variance": "Cost variance: Total Budgeted Costs - Total Actual Cost. Positive values indicate under-budget, negative values indicate over-budget."
|
"variance": "成本偏差:总预算成本 - 总实际成本。正值表示预算内,负值表示超预算。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,5 +48,8 @@
|
|||||||
"jobTitleCreatedSuccess": "职位名称创建成功",
|
"jobTitleCreatedSuccess": "职位名称创建成功",
|
||||||
"jobTitleCreateError": "职位名称创建失败",
|
"jobTitleCreateError": "职位名称创建失败",
|
||||||
"createButton": "创建",
|
"createButton": "创建",
|
||||||
"cancelButton": "取消"
|
"cancelButton": "取消",
|
||||||
|
"discardButton": "放弃",
|
||||||
|
"manDaysCalculationMessage": "组织正在使用人日计算({{hours}}小时/天)。上述费率代表日费率。",
|
||||||
|
"hourlyCalculationMessage": "组织正在使用小时计算。上述费率代表小时费率。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -303,4 +303,9 @@ export const adminCenterApiService = {
|
|||||||
);
|
);
|
||||||
return response.data;
|
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 [editModalVisible, setEditModalVisible] = useState(false);
|
||||||
const [selectedHoliday, setSelectedHoliday] = useState<IHolidayCalendarEvent | null>(null);
|
const [selectedHoliday, setSelectedHoliday] = useState<IHolidayCalendarEvent | null>(null);
|
||||||
const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs());
|
const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs());
|
||||||
|
const [isPopulatingHolidays, setIsPopulatingHolidays] = useState(false);
|
||||||
|
const [hasAttemptedPopulation, setHasAttemptedPopulation] = useState(false);
|
||||||
|
|
||||||
const fetchHolidayTypes = async () => {
|
const fetchHolidayTypes = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -69,9 +71,18 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
|
|
||||||
const populateHolidaysIfNeeded = async () => {
|
const populateHolidaysIfNeeded = async () => {
|
||||||
// Check if we have holiday settings with a country code but no holidays
|
// 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 {
|
try {
|
||||||
console.log('🔄 No holidays found, attempting to populate official holidays...');
|
console.log('🔄 No holidays found, attempting to populate official holidays...');
|
||||||
|
setIsPopulatingHolidays(true);
|
||||||
|
setHasAttemptedPopulation(true);
|
||||||
|
|
||||||
const populateRes = await holidayApiService.populateCountryHolidays();
|
const populateRes = await holidayApiService.populateCountryHolidays();
|
||||||
if (populateRes.done) {
|
if (populateRes.done) {
|
||||||
console.log('✅ Official holidays populated successfully');
|
console.log('✅ Official holidays populated successfully');
|
||||||
@@ -80,6 +91,8 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('⚠️ Could not populate official holidays:', 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
|
// Check if we need to populate holidays when holiday settings are loaded
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
populateHolidaysIfNeeded();
|
populateHolidaysIfNeeded();
|
||||||
}, [holidaySettings, holidays.length]);
|
}, [holidaySettings]);
|
||||||
|
|
||||||
|
// Reset population attempt state when holiday settings change
|
||||||
|
useEffect(() => {
|
||||||
|
setHasAttemptedPopulation(false);
|
||||||
|
}, [holidaySettings?.country_code]);
|
||||||
|
|
||||||
const customHolidays = useMemo(() => {
|
const customHolidays = useMemo(() => {
|
||||||
return holidays.filter(holiday => holiday.source === 'custom');
|
return holidays.filter(holiday => holiday.source === 'custom');
|
||||||
@@ -300,6 +318,11 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
{holidaySettings.country_code}
|
{holidaySettings.country_code}
|
||||||
{holidaySettings.state_code && ` (${holidaySettings.state_code})`}
|
{holidaySettings.state_code && ` (${holidaySettings.state_code})`}
|
||||||
</span>
|
</span>
|
||||||
|
{isPopulatingHolidays && (
|
||||||
|
<span style={{ marginLeft: 8, color: '#faad14' }}>
|
||||||
|
🔄 Populating official holidays...
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ const RateCardDrawer = ({
|
|||||||
handleDeleteRole(index);
|
handleDeleteRole(index);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip title="Delete">
|
<Tooltip title={t('deleteTooltip') || 'Delete'}>
|
||||||
<Button size="small" icon={<DeleteOutlined />} />
|
<Button size="small" icon={<DeleteOutlined />} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
@@ -504,10 +504,10 @@ const RateCardDrawer = ({
|
|||||||
action={
|
action={
|
||||||
<Space direction="horizontal">
|
<Space direction="horizontal">
|
||||||
<Button size="small" type="primary" onClick={handleConfirmSave}>
|
<Button size="small" type="primary" onClick={handleConfirmSave}>
|
||||||
Save
|
{t('saveButton') || 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="small" danger onClick={handleConfirmDiscard}>
|
<Button size="small" danger onClick={handleConfirmDiscard}>
|
||||||
Discard
|
{t('discardButton') || 'Discard'}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -568,8 +568,10 @@ const RateCardDrawer = ({
|
|||||||
<Alert
|
<Alert
|
||||||
message={
|
message={
|
||||||
isManDaysMethod
|
isManDaysMethod
|
||||||
? `Organization is using man days calculation (${organization.hours_per_day || 8}h/day). Rates above represent daily rates.`
|
? t('manDaysCalculationMessage', {
|
||||||
: 'Organization is using hourly calculation. Rates above represent hourly rates.'
|
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"
|
type="info"
|
||||||
showIcon
|
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(
|
export const fetchOrganizationAdmins = createAsyncThunk(
|
||||||
'adminCenter/fetchOrganizationAdmins',
|
'adminCenter/fetchOrganizationAdmins',
|
||||||
async () => {
|
async () => {
|
||||||
@@ -207,6 +215,17 @@ const adminCenterSlice = createSlice({
|
|||||||
state.loadingOrganization = false;
|
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) => {
|
builder.addCase(fetchOrganizationAdmins.pending, (state, action) => {
|
||||||
state.loadingOrganizationAdmins = true;
|
state.loadingOrganizationAdmins = true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { PageHeader } from '@ant-design/pro-components';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import logger from '@/utils/errorLogger';
|
import logger from '@/utils/errorLogger';
|
||||||
import { scheduleAPIService } from '@/api/schedule/schedule.api.service';
|
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 { Settings } from '@/types/schedule/schedule-v2.types';
|
||||||
import OrganizationCalculationMethod from '@/components/admin-center/overview/organization-calculation-method/organization-calculation-method';
|
import OrganizationCalculationMethod from '@/components/admin-center/overview/organization-calculation-method/organization-calculation-method';
|
||||||
import HolidayCalendar from '@/components/admin-center/overview/holiday-calendar/holiday-calendar';
|
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 { RootState } from '@/app/store';
|
||||||
import {
|
import {
|
||||||
fetchOrganizationDetails,
|
fetchOrganizationDetails,
|
||||||
|
fetchAdminCenterSettings,
|
||||||
fetchOrganizationAdmins,
|
fetchOrganizationAdmins,
|
||||||
fetchHolidaySettings,
|
fetchHolidaySettings,
|
||||||
updateHolidaySettings,
|
updateHolidaySettings,
|
||||||
@@ -49,11 +51,11 @@ const SettingsPage: React.FC = () => {
|
|||||||
|
|
||||||
const { t } = useTranslation('admin-center/settings');
|
const { t } = useTranslation('admin-center/settings');
|
||||||
|
|
||||||
const getOrganizationDetails = async () => {
|
const getAdminCenterSettings = async () => {
|
||||||
try {
|
try {
|
||||||
await dispatch(fetchOrganizationDetails()).unwrap();
|
await dispatch(fetchAdminCenterSettings()).unwrap();
|
||||||
} catch (error) {
|
} 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(() => {
|
useEffect(() => {
|
||||||
getOrganizationDetails();
|
getAdminCenterSettings();
|
||||||
getOrganizationAdmins();
|
getOrganizationAdmins();
|
||||||
getOrgWorkingSettings();
|
getOrgWorkingSettings();
|
||||||
dispatch(fetchHolidaySettings());
|
dispatch(fetchHolidaySettings());
|
||||||
@@ -202,10 +204,12 @@ const SettingsPage: React.FC = () => {
|
|||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<OrganizationCalculationMethod
|
{organization && (
|
||||||
organization={organization}
|
<OrganizationCalculationMethod
|
||||||
refetch={getOrganizationDetails}
|
organization={organization}
|
||||||
/>
|
refetch={getAdminCenterSettings}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user