feat(holiday-settings): implement organization holiday settings management
- Added SQL migration for creating organization holiday settings and state holidays tables with necessary constraints and indexes. - Implemented API endpoints in AdminCenterController for retrieving and updating organization holiday settings. - Updated admin-center API router to include routes for holiday settings management. - Enhanced localization files to support new holiday settings UI elements in multiple languages. - Improved holiday calendar component to display working days and integrate holiday settings.
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
-- Create organization holiday settings table
|
||||||
|
CREATE TABLE IF NOT EXISTS organization_holiday_settings (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() NOT NULL,
|
||||||
|
organization_id UUID NOT NULL,
|
||||||
|
country_code CHAR(2),
|
||||||
|
state_code TEXT,
|
||||||
|
auto_sync_holidays BOOLEAN DEFAULT TRUE NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE organization_holiday_settings
|
||||||
|
ADD CONSTRAINT organization_holiday_settings_pk
|
||||||
|
PRIMARY KEY (id);
|
||||||
|
|
||||||
|
ALTER TABLE organization_holiday_settings
|
||||||
|
ADD CONSTRAINT organization_holiday_settings_organization_id_fk
|
||||||
|
FOREIGN KEY (organization_id) REFERENCES organizations
|
||||||
|
ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE organization_holiday_settings
|
||||||
|
ADD CONSTRAINT organization_holiday_settings_country_code_fk
|
||||||
|
FOREIGN KEY (country_code) REFERENCES countries(code)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
|
||||||
|
-- Ensure one settings record per organization
|
||||||
|
ALTER TABLE organization_holiday_settings
|
||||||
|
ADD CONSTRAINT organization_holiday_settings_organization_unique
|
||||||
|
UNIQUE (organization_id);
|
||||||
|
|
||||||
|
-- Create index for better performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_organization_holiday_settings_organization_id ON organization_holiday_settings(organization_id);
|
||||||
|
|
||||||
|
-- Add state holidays table for more granular holiday data
|
||||||
|
CREATE TABLE IF NOT EXISTS state_holidays (
|
||||||
|
id UUID DEFAULT uuid_generate_v4() NOT NULL,
|
||||||
|
country_code CHAR(2) NOT NULL,
|
||||||
|
state_code TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
date DATE NOT NULL,
|
||||||
|
is_recurring BOOLEAN DEFAULT TRUE NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE state_holidays
|
||||||
|
ADD CONSTRAINT state_holidays_pk
|
||||||
|
PRIMARY KEY (id);
|
||||||
|
|
||||||
|
ALTER TABLE state_holidays
|
||||||
|
ADD CONSTRAINT state_holidays_country_code_fk
|
||||||
|
FOREIGN KEY (country_code) REFERENCES countries(code)
|
||||||
|
ON DELETE CASCADE;
|
||||||
|
|
||||||
|
-- Add unique constraint to prevent duplicate holidays for the same state, name, and date
|
||||||
|
ALTER TABLE state_holidays
|
||||||
|
ADD CONSTRAINT state_holidays_state_name_date_unique
|
||||||
|
UNIQUE (country_code, state_code, name, date);
|
||||||
|
|
||||||
|
-- Create indexes for better performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_state_holidays_country_state ON state_holidays(country_code, state_code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_state_holidays_date ON state_holidays(date);
|
||||||
@@ -1119,4 +1119,115 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, response));
|
return res.status(200).send(new ServerResponse(true, response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HandleExceptions()
|
||||||
|
public static async getOrganizationHolidaySettings(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const q = `SELECT ohs.id, ohs.organization_id, ohs.country_code, ohs.state_code,
|
||||||
|
ohs.auto_sync_holidays, ohs.created_at, ohs.updated_at
|
||||||
|
FROM organization_holiday_settings ohs
|
||||||
|
JOIN organizations o ON ohs.organization_id = o.id
|
||||||
|
WHERE o.user_id = $1;`;
|
||||||
|
|
||||||
|
const result = await db.query(q, [req.user?.owner_id]);
|
||||||
|
|
||||||
|
// If no settings exist, return default settings
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
return res.status(200).send(new ServerResponse(true, {
|
||||||
|
country_code: null,
|
||||||
|
state_code: null,
|
||||||
|
auto_sync_holidays: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(new ServerResponse(true, result.rows[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@HandleExceptions()
|
||||||
|
public static async updateOrganizationHolidaySettings(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
const { country_code, state_code, auto_sync_holidays } = req.body;
|
||||||
|
|
||||||
|
// First, get the organization ID
|
||||||
|
const orgQ = `SELECT id FROM organizations WHERE user_id = $1;`;
|
||||||
|
const orgResult = await db.query(orgQ, [req.user?.owner_id]);
|
||||||
|
|
||||||
|
if (orgResult.rows.length === 0) {
|
||||||
|
return res.status(404).send(new ServerResponse(false, "Organization not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const organizationId = orgResult.rows[0].id;
|
||||||
|
|
||||||
|
// Check if settings already exist
|
||||||
|
const checkQ = `SELECT id FROM organization_holiday_settings WHERE organization_id = $1;`;
|
||||||
|
const checkResult = await db.query(checkQ, [organizationId]);
|
||||||
|
|
||||||
|
let result;
|
||||||
|
if (checkResult.rows.length > 0) {
|
||||||
|
// Update existing settings
|
||||||
|
const updateQ = `UPDATE organization_holiday_settings
|
||||||
|
SET country_code = $2,
|
||||||
|
state_code = $3,
|
||||||
|
auto_sync_holidays = $4,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE organization_id = $1
|
||||||
|
RETURNING *;`;
|
||||||
|
result = await db.query(updateQ, [organizationId, country_code, state_code, auto_sync_holidays]);
|
||||||
|
} else {
|
||||||
|
// Insert new settings
|
||||||
|
const insertQ = `INSERT INTO organization_holiday_settings
|
||||||
|
(organization_id, country_code, state_code, auto_sync_holidays)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
RETURNING *;`;
|
||||||
|
result = await db.query(insertQ, [organizationId, country_code, state_code, auto_sync_holidays]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(new ServerResponse(true, result.rows[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@HandleExceptions()
|
||||||
|
public static async getCountriesWithStates(
|
||||||
|
req: IWorkLenzRequest,
|
||||||
|
res: IWorkLenzResponse
|
||||||
|
): Promise<IWorkLenzResponse> {
|
||||||
|
// Get all countries
|
||||||
|
const countriesQ = `SELECT code, name FROM countries ORDER BY name;`;
|
||||||
|
const countriesResult = await db.query(countriesQ);
|
||||||
|
|
||||||
|
// For now, we'll return a basic structure
|
||||||
|
// In a real implementation, you would have a states table
|
||||||
|
const countriesWithStates = countriesResult.rows.map(country => ({
|
||||||
|
code: country.code,
|
||||||
|
name: country.name,
|
||||||
|
states: [] as Array<{ code: string; name: string }> // Would be populated from a states table
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Add some example states for US and Canada
|
||||||
|
const usIndex = countriesWithStates.findIndex(c => c.code === 'US');
|
||||||
|
if (usIndex !== -1) {
|
||||||
|
countriesWithStates[usIndex].states = [
|
||||||
|
{ code: 'CA', name: 'California' },
|
||||||
|
{ code: 'NY', name: 'New York' },
|
||||||
|
{ code: 'TX', name: 'Texas' },
|
||||||
|
{ code: 'FL', name: 'Florida' },
|
||||||
|
{ code: 'WA', name: 'Washington' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const caIndex = countriesWithStates.findIndex(c => c.code === 'CA');
|
||||||
|
if (caIndex !== -1) {
|
||||||
|
countriesWithStates[caIndex].states = [
|
||||||
|
{ code: 'ON', name: 'Ontario' },
|
||||||
|
{ code: 'QC', name: 'Quebec' },
|
||||||
|
{ code: 'BC', name: 'British Columbia' },
|
||||||
|
{ code: 'AB', name: 'Alberta' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(new ServerResponse(true, countriesWithStates));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ adminCenterApiRouter.put("/organization", teamOwnerOrAdminValidator, organizatio
|
|||||||
adminCenterApiRouter.put("/organization/calculation-method", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.updateOrganizationCalculationMethod));
|
adminCenterApiRouter.put("/organization/calculation-method", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.updateOrganizationCalculationMethod));
|
||||||
adminCenterApiRouter.put("/organization/owner/contact-number", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.updateOwnerContactNumber));
|
adminCenterApiRouter.put("/organization/owner/contact-number", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.updateOwnerContactNumber));
|
||||||
|
|
||||||
|
// holiday settings
|
||||||
|
adminCenterApiRouter.get("/organization/holiday-settings", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationHolidaySettings));
|
||||||
|
adminCenterApiRouter.put("/organization/holiday-settings", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.updateOrganizationHolidaySettings));
|
||||||
|
adminCenterApiRouter.get("/countries-with-states", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getCountriesWithStates));
|
||||||
|
|
||||||
// users
|
// users
|
||||||
adminCenterApiRouter.get("/organization/users", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationUsers));
|
adminCenterApiRouter.get("/organization/users", teamOwnerOrAdminValidator, safeControllerFunction(AdminCenterController.getOrganizationUsers));
|
||||||
|
|
||||||
|
|||||||
@@ -28,5 +28,45 @@
|
|||||||
"manDaysCalculationDescription": "Të gjitha kostot e projektit do të llogariten duke përdorur ditët e vlerësuara të njeriut × normat ditore",
|
"manDaysCalculationDescription": "Të gjitha kostot e projektit do të llogariten duke përdorur ditët e vlerësuara të njeriut × normat ditore",
|
||||||
"calculationMethodTooltip": "Ky cilësim zbatohet për të gjitha projektet në organizatën tuaj",
|
"calculationMethodTooltip": "Ky cilësim zbatohet për të gjitha projektet në organizatën tuaj",
|
||||||
"calculationMethodUpdated": "Metoda e llogaritjes së organizatës u përditësua me sukses",
|
"calculationMethodUpdated": "Metoda e llogaritjes së organizatës u përditësua me sukses",
|
||||||
"calculationMethodUpdateError": "Dështoi përditësimi i metodës së llogaritjes"
|
"calculationMethodUpdateError": "Dështoi përditësimi i metodës së llogaritjes",
|
||||||
|
"holidayCalendar": "Kalnedari i Festave",
|
||||||
|
"addHoliday": "Shto Festë",
|
||||||
|
"editHoliday": "Redakto Festë",
|
||||||
|
"holidayName": "Emri i Festës",
|
||||||
|
"holidayNameRequired": "Ju lutemi shkruani emrin e festës",
|
||||||
|
"description": "Përshkrim",
|
||||||
|
"date": "Data",
|
||||||
|
"dateRequired": "Ju lutemi zgjidhni një datë",
|
||||||
|
"holidayType": "Lloji i Festës",
|
||||||
|
"holidayTypeRequired": "Ju lutemi zgjidhni një lloj feste",
|
||||||
|
"recurring": "Përsëritëse",
|
||||||
|
"save": "Ruaj",
|
||||||
|
"update": "Përditëso",
|
||||||
|
"cancel": "Anulo",
|
||||||
|
"holidayCreated": "Festa u krijua me sukses",
|
||||||
|
"holidayUpdated": "Festa u përditësua me sukses",
|
||||||
|
"holidayDeleted": "Festa u fshi me sukses",
|
||||||
|
"errorCreatingHoliday": "Gabim gjatë krijimit të festës",
|
||||||
|
"errorUpdatingHoliday": "Gabim gjatë përditësimit të festës",
|
||||||
|
"errorDeletingHoliday": "Gabim gjatë fshirjes së festës",
|
||||||
|
"importCountryHolidays": "Importo Festat e Vendit",
|
||||||
|
"country": "Vendi",
|
||||||
|
"countryRequired": "Ju lutemi zgjidhni një vend",
|
||||||
|
"selectCountry": "Zgjidhni një vend",
|
||||||
|
"year": "Viti",
|
||||||
|
"import": "Importo",
|
||||||
|
"holidaysImported": "U importuan me sukses {{count}} festa",
|
||||||
|
"errorImportingHolidays": "Gabim gjatë importimit të festave",
|
||||||
|
"addCustomHoliday": "Shto Festë të Përshtatur",
|
||||||
|
"officialHolidaysFrom": "Festat zyrtare nga",
|
||||||
|
"workingDay": "Ditë Pune",
|
||||||
|
"holiday": "Festë",
|
||||||
|
"today": "Sot",
|
||||||
|
"cannotEditOfficialHoliday": "Nuk mund të redaktoni festat zyrtare",
|
||||||
|
"customHoliday": "Festë e Përshtatur",
|
||||||
|
"officialHoliday": "Festë Zyrtare",
|
||||||
|
"delete": "Fshi",
|
||||||
|
"deleteHolidayConfirm": "A jeni i sigurt që dëshironi të fshini këtë festë?",
|
||||||
|
"yes": "Po",
|
||||||
|
"no": "Jo"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,21 @@
|
|||||||
"sunday": "E Dielë",
|
"sunday": "E Dielë",
|
||||||
"saveButton": "Ruaj",
|
"saveButton": "Ruaj",
|
||||||
"saved": "Cilësimet u ruajtën me sukses",
|
"saved": "Cilësimet u ruajtën me sukses",
|
||||||
"errorSaving": "Gabim gjatë ruajtjes së cilësimeve"
|
"errorSaving": "Gabim gjatë ruajtjes së cilësimeve",
|
||||||
|
"holidaySettings": "Cilësimet e pushimeve",
|
||||||
|
"country": "Vendi",
|
||||||
|
"countryRequired": "Ju lutemi zgjidhni një vend",
|
||||||
|
"selectCountry": "Zgjidhni vendin",
|
||||||
|
"state": "Shteti/Provinca",
|
||||||
|
"selectState": "Zgjidhni shtetin/provincën (opsionale)",
|
||||||
|
"autoSyncHolidays": "Sinkronizo automatikisht pushimet zyrtare",
|
||||||
|
"saveHolidaySettings": "Ruaj cilësimet e pushimeve",
|
||||||
|
"holidaySettingsSaved": "Cilësimet e pushimeve u ruajtën me sukses",
|
||||||
|
"errorSavingHolidaySettings": "Gabim gjatë ruajtjes së cilësimeve të pushimeve",
|
||||||
|
"addCustomHoliday": "Shto Festë të Përshtatur",
|
||||||
|
"officialHolidaysFrom": "Festat zyrtare nga",
|
||||||
|
"workingDay": "Ditë Pune",
|
||||||
|
"holiday": "Festë",
|
||||||
|
"today": "Sot",
|
||||||
|
"cannotEditOfficialHoliday": "Nuk mund të redaktoni festat zyrtare"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,5 +28,45 @@
|
|||||||
"manDaysCalculationDescription": "Alle Projektkosten werden anhand geschätzter Mann-Tage × Tagessätze berechnet",
|
"manDaysCalculationDescription": "Alle Projektkosten werden anhand geschätzter Mann-Tage × Tagessätze berechnet",
|
||||||
"calculationMethodTooltip": "Diese Einstellung gilt für alle Projekte in Ihrer Organisation",
|
"calculationMethodTooltip": "Diese Einstellung gilt für alle Projekte in Ihrer Organisation",
|
||||||
"calculationMethodUpdated": "Organisations-Berechnungsmethode erfolgreich aktualisiert",
|
"calculationMethodUpdated": "Organisations-Berechnungsmethode erfolgreich aktualisiert",
|
||||||
"calculationMethodUpdateError": "Fehler beim Aktualisieren der Berechnungsmethode"
|
"calculationMethodUpdateError": "Fehler beim Aktualisieren der Berechnungsmethode",
|
||||||
|
"holidayCalendar": "Feiertagskalender",
|
||||||
|
"addHoliday": "Feiertag hinzufügen",
|
||||||
|
"editHoliday": "Feiertag bearbeiten",
|
||||||
|
"holidayName": "Feiertagsname",
|
||||||
|
"holidayNameRequired": "Bitte geben Sie den Feiertagsnamen ein",
|
||||||
|
"description": "Beschreibung",
|
||||||
|
"date": "Datum",
|
||||||
|
"dateRequired": "Bitte wählen Sie ein Datum aus",
|
||||||
|
"holidayType": "Feiertagstyp",
|
||||||
|
"holidayTypeRequired": "Bitte wählen Sie einen Feiertagstyp aus",
|
||||||
|
"recurring": "Wiederkehrend",
|
||||||
|
"save": "Speichern",
|
||||||
|
"update": "Aktualisieren",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"holidayCreated": "Feiertag erfolgreich erstellt",
|
||||||
|
"holidayUpdated": "Feiertag erfolgreich aktualisiert",
|
||||||
|
"holidayDeleted": "Feiertag erfolgreich gelöscht",
|
||||||
|
"errorCreatingHoliday": "Fehler beim Erstellen des Feiertags",
|
||||||
|
"errorUpdatingHoliday": "Fehler beim Aktualisieren des Feiertags",
|
||||||
|
"errorDeletingHoliday": "Fehler beim Löschen des Feiertags",
|
||||||
|
"importCountryHolidays": "Landesfeiertage importieren",
|
||||||
|
"country": "Land",
|
||||||
|
"countryRequired": "Bitte wählen Sie ein Land aus",
|
||||||
|
"selectCountry": "Ein Land auswählen",
|
||||||
|
"year": "Jahr",
|
||||||
|
"import": "Importieren",
|
||||||
|
"holidaysImported": "{{count}} Feiertage erfolgreich importiert",
|
||||||
|
"errorImportingHolidays": "Fehler beim Importieren der Feiertage",
|
||||||
|
"addCustomHoliday": "Benutzerdefinierten Feiertag hinzufügen",
|
||||||
|
"officialHolidaysFrom": "Offizielle Feiertage aus",
|
||||||
|
"workingDay": "Arbeitstag",
|
||||||
|
"holiday": "Feiertag",
|
||||||
|
"today": "Heute",
|
||||||
|
"cannotEditOfficialHoliday": "Offizielle Feiertage können nicht bearbeitet werden",
|
||||||
|
"customHoliday": "Benutzerdefinierter Feiertag",
|
||||||
|
"officialHoliday": "Offizieller Feiertag",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"deleteHolidayConfirm": "Sind Sie sicher, dass Sie diesen Feiertag löschen möchten?",
|
||||||
|
"yes": "Ja",
|
||||||
|
"no": "Nein"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,21 @@
|
|||||||
"sunday": "Sonntag",
|
"sunday": "Sonntag",
|
||||||
"saveButton": "Speichern",
|
"saveButton": "Speichern",
|
||||||
"saved": "Einstellungen erfolgreich gespeichert",
|
"saved": "Einstellungen erfolgreich gespeichert",
|
||||||
"errorSaving": "Fehler beim Speichern der Einstellungen"
|
"errorSaving": "Fehler beim Speichern der Einstellungen",
|
||||||
|
"holidaySettings": "Feiertagseinstellungen",
|
||||||
|
"country": "Land",
|
||||||
|
"countryRequired": "Bitte wählen Sie ein Land aus",
|
||||||
|
"selectCountry": "Land auswählen",
|
||||||
|
"state": "Bundesland/Provinz",
|
||||||
|
"selectState": "Bundesland/Provinz auswählen (optional)",
|
||||||
|
"autoSyncHolidays": "Offizielle Feiertage automatisch synchronisieren",
|
||||||
|
"saveHolidaySettings": "Feiertagseinstellungen speichern",
|
||||||
|
"holidaySettingsSaved": "Feiertagseinstellungen erfolgreich gespeichert",
|
||||||
|
"errorSavingHolidaySettings": "Fehler beim Speichern der Feiertagseinstellungen",
|
||||||
|
"addCustomHoliday": "Benutzerdefinierten Feiertag hinzufügen",
|
||||||
|
"officialHolidaysFrom": "Offizielle Feiertage aus",
|
||||||
|
"workingDay": "Arbeitstag",
|
||||||
|
"holiday": "Feiertag",
|
||||||
|
"today": "Heute",
|
||||||
|
"cannotEditOfficialHoliday": "Offizielle Feiertage können nicht bearbeitet werden"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,5 +56,17 @@
|
|||||||
"year": "Year",
|
"year": "Year",
|
||||||
"import": "Import",
|
"import": "Import",
|
||||||
"holidaysImported": "Successfully imported {{count}} holidays",
|
"holidaysImported": "Successfully imported {{count}} holidays",
|
||||||
"errorImportingHolidays": "Error importing holidays"
|
"errorImportingHolidays": "Error importing holidays",
|
||||||
|
"addCustomHoliday": "Add Custom Holiday",
|
||||||
|
"officialHolidaysFrom": "Official holidays from",
|
||||||
|
"workingDay": "Working Day",
|
||||||
|
"holiday": "Holiday",
|
||||||
|
"today": "Today",
|
||||||
|
"cannotEditOfficialHoliday": "Cannot edit official holidays",
|
||||||
|
"customHoliday": "Custom Holiday",
|
||||||
|
"officialHoliday": "Official Holiday",
|
||||||
|
"delete": "Delete",
|
||||||
|
"deleteHolidayConfirm": "Are you sure you want to delete this holiday?",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,15 @@
|
|||||||
"sunday": "Sunday",
|
"sunday": "Sunday",
|
||||||
"saveButton": "Save",
|
"saveButton": "Save",
|
||||||
"saved": "Settings saved successfully",
|
"saved": "Settings saved successfully",
|
||||||
"errorSaving": "Error saving settings"
|
"errorSaving": "Error saving settings",
|
||||||
|
"holidaySettings": "Holiday Settings",
|
||||||
|
"country": "Country",
|
||||||
|
"countryRequired": "Please select a country",
|
||||||
|
"selectCountry": "Select country",
|
||||||
|
"state": "State/Province",
|
||||||
|
"selectState": "Select state/province (optional)",
|
||||||
|
"autoSyncHolidays": "Automatically sync official holidays",
|
||||||
|
"saveHolidaySettings": "Save Holiday Settings",
|
||||||
|
"holidaySettingsSaved": "Holiday settings saved successfully",
|
||||||
|
"errorSavingHolidaySettings": "Error saving holiday settings"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,5 +28,45 @@
|
|||||||
"manDaysCalculationDescription": "Todos los costos del proyecto se calcularán usando días hombre estimados × tarifas diarias",
|
"manDaysCalculationDescription": "Todos los costos del proyecto se calcularán usando días hombre estimados × tarifas diarias",
|
||||||
"calculationMethodTooltip": "Esta configuración se aplica a todos los proyectos en su organización",
|
"calculationMethodTooltip": "Esta configuración se aplica a todos los proyectos en su organización",
|
||||||
"calculationMethodUpdated": "Método de cálculo de la organización actualizado exitosamente",
|
"calculationMethodUpdated": "Método de cálculo de la organización actualizado exitosamente",
|
||||||
"calculationMethodUpdateError": "Error al actualizar el método de cálculo"
|
"calculationMethodUpdateError": "Error al actualizar el método de cálculo",
|
||||||
|
"holidayCalendar": "Calendario de Días Festivos",
|
||||||
|
"addHoliday": "Agregar Día Festivo",
|
||||||
|
"editHoliday": "Editar Día Festivo",
|
||||||
|
"holidayName": "Nombre del Día Festivo",
|
||||||
|
"holidayNameRequired": "Por favor ingrese el nombre del día festivo",
|
||||||
|
"description": "Descripción",
|
||||||
|
"date": "Fecha",
|
||||||
|
"dateRequired": "Por favor seleccione una fecha",
|
||||||
|
"holidayType": "Tipo de Día Festivo",
|
||||||
|
"holidayTypeRequired": "Por favor seleccione un tipo de día festivo",
|
||||||
|
"recurring": "Recurrente",
|
||||||
|
"save": "Guardar",
|
||||||
|
"update": "Actualizar",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"holidayCreated": "Día festivo creado exitosamente",
|
||||||
|
"holidayUpdated": "Día festivo actualizado exitosamente",
|
||||||
|
"holidayDeleted": "Día festivo eliminado exitosamente",
|
||||||
|
"errorCreatingHoliday": "Error al crear el día festivo",
|
||||||
|
"errorUpdatingHoliday": "Error al actualizar el día festivo",
|
||||||
|
"errorDeletingHoliday": "Error al eliminar el día festivo",
|
||||||
|
"importCountryHolidays": "Importar Días Festivos del País",
|
||||||
|
"country": "País",
|
||||||
|
"countryRequired": "Por favor seleccione un país",
|
||||||
|
"selectCountry": "Seleccionar un país",
|
||||||
|
"year": "Año",
|
||||||
|
"import": "Importar",
|
||||||
|
"holidaysImported": "{{count}} días festivos importados exitosamente",
|
||||||
|
"errorImportingHolidays": "Error al importar días festivos",
|
||||||
|
"addCustomHoliday": "Agregar Día Festivo Personalizado",
|
||||||
|
"officialHolidaysFrom": "Días festivos oficiales de",
|
||||||
|
"workingDay": "Día Laboral",
|
||||||
|
"holiday": "Día Festivo",
|
||||||
|
"today": "Hoy",
|
||||||
|
"cannotEditOfficialHoliday": "No se pueden editar los días festivos oficiales",
|
||||||
|
"customHoliday": "Día Festivo Personalizado",
|
||||||
|
"officialHoliday": "Día Festivo Oficial",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"deleteHolidayConfirm": "¿Está seguro de que desea eliminar este día festivo?",
|
||||||
|
"yes": "Sí",
|
||||||
|
"no": "No"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,21 @@
|
|||||||
"sunday": "Domingo",
|
"sunday": "Domingo",
|
||||||
"saveButton": "Guardar",
|
"saveButton": "Guardar",
|
||||||
"saved": "Configuración guardada exitosamente",
|
"saved": "Configuración guardada exitosamente",
|
||||||
"errorSaving": "Error al guardar la configuración"
|
"errorSaving": "Error al guardar la configuración",
|
||||||
|
"holidaySettings": "Configuración de días festivos",
|
||||||
|
"country": "País",
|
||||||
|
"countryRequired": "Por favor seleccione un país",
|
||||||
|
"selectCountry": "Seleccionar país",
|
||||||
|
"state": "Estado/Provincia",
|
||||||
|
"selectState": "Seleccionar estado/provincia (opcional)",
|
||||||
|
"autoSyncHolidays": "Sincronizar automáticamente los días festivos oficiales",
|
||||||
|
"saveHolidaySettings": "Guardar configuración de días festivos",
|
||||||
|
"holidaySettingsSaved": "Configuración de días festivos guardada exitosamente",
|
||||||
|
"errorSavingHolidaySettings": "Error al guardar la configuración de días festivos",
|
||||||
|
"addCustomHoliday": "Agregar Día Festivo Personalizado",
|
||||||
|
"officialHolidaysFrom": "Días festivos oficiales de",
|
||||||
|
"workingDay": "Día Laboral",
|
||||||
|
"holiday": "Día Festivo",
|
||||||
|
"today": "Hoy",
|
||||||
|
"cannotEditOfficialHoliday": "No se pueden editar los días festivos oficiales"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,5 +28,45 @@
|
|||||||
"manDaysCalculationDescription": "Todos os custos do projeto serão calculados usando dias homem estimados × taxas diárias",
|
"manDaysCalculationDescription": "Todos os custos do projeto serão calculados usando dias homem estimados × taxas diárias",
|
||||||
"calculationMethodTooltip": "Esta configuração se aplica a todos os projetos em sua organização",
|
"calculationMethodTooltip": "Esta configuração se aplica a todos os projetos em sua organização",
|
||||||
"calculationMethodUpdated": "Método de cálculo da organização atualizado com sucesso",
|
"calculationMethodUpdated": "Método de cálculo da organização atualizado com sucesso",
|
||||||
"calculationMethodUpdateError": "Erro ao atualizar o método de cálculo"
|
"calculationMethodUpdateError": "Erro ao atualizar o método de cálculo",
|
||||||
|
"holidayCalendar": "Calendário de Feriados",
|
||||||
|
"addHoliday": "Adicionar Feriado",
|
||||||
|
"editHoliday": "Editar Feriado",
|
||||||
|
"holidayName": "Nome do Feriado",
|
||||||
|
"holidayNameRequired": "Por favor, digite o nome do feriado",
|
||||||
|
"description": "Descrição",
|
||||||
|
"date": "Data",
|
||||||
|
"dateRequired": "Por favor, selecione uma data",
|
||||||
|
"holidayType": "Tipo de Feriado",
|
||||||
|
"holidayTypeRequired": "Por favor, selecione um tipo de feriado",
|
||||||
|
"recurring": "Recorrente",
|
||||||
|
"save": "Salvar",
|
||||||
|
"update": "Atualizar",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"holidayCreated": "Feriado criado com sucesso",
|
||||||
|
"holidayUpdated": "Feriado atualizado com sucesso",
|
||||||
|
"holidayDeleted": "Feriado excluído com sucesso",
|
||||||
|
"errorCreatingHoliday": "Erro ao criar feriado",
|
||||||
|
"errorUpdatingHoliday": "Erro ao atualizar feriado",
|
||||||
|
"errorDeletingHoliday": "Erro ao excluir feriado",
|
||||||
|
"importCountryHolidays": "Importar Feriados do País",
|
||||||
|
"country": "País",
|
||||||
|
"countryRequired": "Por favor, selecione um país",
|
||||||
|
"selectCountry": "Selecionar um país",
|
||||||
|
"year": "Ano",
|
||||||
|
"import": "Importar",
|
||||||
|
"holidaysImported": "{{count}} feriados importados com sucesso",
|
||||||
|
"errorImportingHolidays": "Erro ao importar feriados",
|
||||||
|
"addCustomHoliday": "Adicionar Feriado Personalizado",
|
||||||
|
"officialHolidaysFrom": "Feriados oficiais de",
|
||||||
|
"workingDay": "Dia de Trabalho",
|
||||||
|
"holiday": "Feriado",
|
||||||
|
"today": "Hoje",
|
||||||
|
"cannotEditOfficialHoliday": "Não é possível editar feriados oficiais",
|
||||||
|
"customHoliday": "Feriado Personalizado",
|
||||||
|
"officialHoliday": "Feriado Oficial",
|
||||||
|
"delete": "Excluir",
|
||||||
|
"deleteHolidayConfirm": "Tem certeza de que deseja excluir este feriado?",
|
||||||
|
"yes": "Sim",
|
||||||
|
"no": "Não"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,21 @@
|
|||||||
"sunday": "Domingo",
|
"sunday": "Domingo",
|
||||||
"saveButton": "Salvar",
|
"saveButton": "Salvar",
|
||||||
"saved": "Configurações salvas com sucesso",
|
"saved": "Configurações salvas com sucesso",
|
||||||
"errorSaving": "Erro ao salvar configurações"
|
"errorSaving": "Erro ao salvar configurações",
|
||||||
|
"holidaySettings": "Configurações de feriados",
|
||||||
|
"country": "País",
|
||||||
|
"countryRequired": "Por favor, selecione um país",
|
||||||
|
"selectCountry": "Selecionar país",
|
||||||
|
"state": "Estado/Província",
|
||||||
|
"selectState": "Selecionar estado/província (opcional)",
|
||||||
|
"autoSyncHolidays": "Sincronizar automaticamente feriados oficiais",
|
||||||
|
"saveHolidaySettings": "Salvar configurações de feriados",
|
||||||
|
"holidaySettingsSaved": "Configurações de feriados salvas com sucesso",
|
||||||
|
"errorSavingHolidaySettings": "Erro ao salvar configurações de feriados",
|
||||||
|
"addCustomHoliday": "Adicionar Feriado Personalizado",
|
||||||
|
"officialHolidaysFrom": "Feriados oficiais de",
|
||||||
|
"workingDay": "Dia de Trabalho",
|
||||||
|
"holiday": "Feriado",
|
||||||
|
"today": "Hoje",
|
||||||
|
"cannotEditOfficialHoliday": "Não é possível editar feriados oficiais"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,5 +28,45 @@
|
|||||||
"manDaysCalculationDescription": "所有项目成本将使用估算人天数 × 日费率计算",
|
"manDaysCalculationDescription": "所有项目成本将使用估算人天数 × 日费率计算",
|
||||||
"calculationMethodTooltip": "此设置适用于您组织中的所有项目",
|
"calculationMethodTooltip": "此设置适用于您组织中的所有项目",
|
||||||
"calculationMethodUpdated": "组织计算方法更新成功",
|
"calculationMethodUpdated": "组织计算方法更新成功",
|
||||||
"calculationMethodUpdateError": "更新计算方法失败"
|
"calculationMethodUpdateError": "更新计算方法失败",
|
||||||
|
"holidayCalendar": "假期日历",
|
||||||
|
"addHoliday": "添加假期",
|
||||||
|
"editHoliday": "编辑假期",
|
||||||
|
"holidayName": "假期名称",
|
||||||
|
"holidayNameRequired": "请输入假期名称",
|
||||||
|
"description": "描述",
|
||||||
|
"date": "日期",
|
||||||
|
"dateRequired": "请选择日期",
|
||||||
|
"holidayType": "假期类型",
|
||||||
|
"holidayTypeRequired": "请选择假期类型",
|
||||||
|
"recurring": "循环",
|
||||||
|
"save": "保存",
|
||||||
|
"update": "更新",
|
||||||
|
"cancel": "取消",
|
||||||
|
"holidayCreated": "假期创建成功",
|
||||||
|
"holidayUpdated": "假期更新成功",
|
||||||
|
"holidayDeleted": "假期删除成功",
|
||||||
|
"errorCreatingHoliday": "创建假期时出错",
|
||||||
|
"errorUpdatingHoliday": "更新假期时出错",
|
||||||
|
"errorDeletingHoliday": "删除假期时出错",
|
||||||
|
"importCountryHolidays": "导入国家假期",
|
||||||
|
"country": "国家",
|
||||||
|
"countryRequired": "请选择国家",
|
||||||
|
"selectCountry": "选择国家",
|
||||||
|
"year": "年份",
|
||||||
|
"import": "导入",
|
||||||
|
"holidaysImported": "成功导入{{count}}个假期",
|
||||||
|
"errorImportingHolidays": "导入假期时出错",
|
||||||
|
"addCustomHoliday": "添加自定义假期",
|
||||||
|
"officialHolidaysFrom": "官方假期来自",
|
||||||
|
"workingDay": "工作日",
|
||||||
|
"holiday": "假期",
|
||||||
|
"today": "今天",
|
||||||
|
"cannotEditOfficialHoliday": "无法编辑官方假期",
|
||||||
|
"customHoliday": "自定义假期",
|
||||||
|
"officialHoliday": "官方假期",
|
||||||
|
"delete": "删除",
|
||||||
|
"deleteHolidayConfirm": "您确定要删除这个假期吗?",
|
||||||
|
"yes": "是",
|
||||||
|
"no": "否"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,21 @@
|
|||||||
"sunday": "星期日",
|
"sunday": "星期日",
|
||||||
"saveButton": "保存",
|
"saveButton": "保存",
|
||||||
"saved": "设置保存成功",
|
"saved": "设置保存成功",
|
||||||
"errorSaving": "保存设置时出错"
|
"errorSaving": "保存设置时出错",
|
||||||
|
"holidaySettings": "假期设置",
|
||||||
|
"country": "国家",
|
||||||
|
"countryRequired": "请选择一个国家",
|
||||||
|
"selectCountry": "选择国家",
|
||||||
|
"state": "州/省",
|
||||||
|
"selectState": "选择州/省(可选)",
|
||||||
|
"autoSyncHolidays": "自动同步官方假期",
|
||||||
|
"saveHolidaySettings": "保存假期设置",
|
||||||
|
"holidaySettingsSaved": "假期设置保存成功",
|
||||||
|
"errorSavingHolidaySettings": "保存假期设置时出错",
|
||||||
|
"addCustomHoliday": "添加自定义假期",
|
||||||
|
"officialHolidaysFrom": "官方假期来自",
|
||||||
|
"workingDay": "工作日",
|
||||||
|
"holiday": "假期",
|
||||||
|
"today": "今天",
|
||||||
|
"cannotEditOfficialHoliday": "无法编辑官方假期"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,63 +19,51 @@ import {
|
|||||||
const rootUrl = `${API_BASE_URL}/holidays`;
|
const rootUrl = `${API_BASE_URL}/holidays`;
|
||||||
|
|
||||||
export const holidayApiService = {
|
export const holidayApiService = {
|
||||||
// Holiday types - PLACEHOLDER with Sri Lankan specific types
|
// Holiday types
|
||||||
getHolidayTypes: async (): Promise<IServerResponse<IHolidayType[]>> => {
|
getHolidayTypes: async (): Promise<IServerResponse<IHolidayType[]>> => {
|
||||||
// Return holiday types including Sri Lankan specific types
|
const response = await apiClient.get<IServerResponse<IHolidayType[]>>(
|
||||||
const holidayTypes = [
|
`${rootUrl}/types`
|
||||||
{ id: '1', name: 'Public Holiday', color_code: '#DC143C' },
|
);
|
||||||
{ id: '2', name: 'Religious Holiday', color_code: '#4ecdc4' },
|
return response.data;
|
||||||
{ id: '3', name: 'National Holiday', color_code: '#45b7d1' },
|
|
||||||
{ id: '4', name: 'Company Holiday', color_code: '#f9ca24' },
|
|
||||||
{ id: '5', name: 'Personal Holiday', color_code: '#6c5ce7' },
|
|
||||||
{ id: '6', name: 'Bank Holiday', color_code: '#4682B4' },
|
|
||||||
{ id: '7', name: 'Mercantile Holiday', color_code: '#32CD32' },
|
|
||||||
{ id: '8', name: 'Poya Day', color_code: '#8B4513' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
done: true,
|
|
||||||
body: holidayTypes,
|
|
||||||
} as IServerResponse<IHolidayType[]>;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Organization holidays - PLACEHOLDER until backend implements
|
// Organization holidays
|
||||||
getOrganizationHolidays: async (
|
getOrganizationHolidays: async (
|
||||||
year?: number
|
year?: number
|
||||||
): Promise<IServerResponse<IOrganizationHoliday[]>> => {
|
): Promise<IServerResponse<IOrganizationHoliday[]>> => {
|
||||||
// Return empty array for now to prevent 404 errors
|
const params = year ? { year } : {};
|
||||||
return {
|
const response = await apiClient.get<IServerResponse<IOrganizationHoliday[]>>(
|
||||||
done: true,
|
`${rootUrl}/organization`,
|
||||||
body: [],
|
{ params }
|
||||||
} as IServerResponse<IOrganizationHoliday[]>;
|
);
|
||||||
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Holiday CRUD operations - PLACEHOLDER until backend implements
|
// Holiday CRUD operations
|
||||||
createOrganizationHoliday: async (data: ICreateHolidayRequest): Promise<IServerResponse<any>> => {
|
createOrganizationHoliday: async (data: ICreateHolidayRequest): Promise<IServerResponse<any>> => {
|
||||||
// Return success for now to prevent UI errors
|
const response = await apiClient.post<IServerResponse<any>>(
|
||||||
return {
|
`${rootUrl}/organization`,
|
||||||
done: true,
|
data
|
||||||
body: { id: Date.now().toString(), ...data },
|
);
|
||||||
} as IServerResponse<any>;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateOrganizationHoliday: async (
|
updateOrganizationHoliday: async (
|
||||||
id: string,
|
id: string,
|
||||||
data: IUpdateHolidayRequest
|
data: IUpdateHolidayRequest
|
||||||
): Promise<IServerResponse<any>> => {
|
): Promise<IServerResponse<any>> => {
|
||||||
// Return success for now to prevent UI errors
|
const response = await apiClient.put<IServerResponse<any>>(
|
||||||
return {
|
`${rootUrl}/organization/${id}`,
|
||||||
done: true,
|
data
|
||||||
body: { id, ...data },
|
);
|
||||||
} as IServerResponse<any>;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteOrganizationHoliday: async (id: string): Promise<IServerResponse<any>> => {
|
deleteOrganizationHoliday: async (id: string): Promise<IServerResponse<any>> => {
|
||||||
// Return success for now to prevent UI errors
|
const response = await apiClient.delete<IServerResponse<any>>(
|
||||||
return {
|
`${rootUrl}/organization/${id}`
|
||||||
done: true,
|
);
|
||||||
body: {},
|
return response.data;
|
||||||
} as IServerResponse<any>;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Country holidays - PLACEHOLDER with all date-holidays supported countries
|
// Country holidays - PLACEHOLDER with all date-holidays supported countries
|
||||||
@@ -200,11 +188,15 @@ export const holidayApiService = {
|
|||||||
countryCode: string,
|
countryCode: string,
|
||||||
year?: number
|
year?: number
|
||||||
): Promise<IServerResponse<ICountryHoliday[]>> => {
|
): Promise<IServerResponse<ICountryHoliday[]>> => {
|
||||||
// Return empty array for now
|
const params: any = { country_code: countryCode };
|
||||||
return {
|
if (year) {
|
||||||
done: true,
|
params.year = year;
|
||||||
body: [],
|
}
|
||||||
} as IServerResponse<ICountryHoliday[]>;
|
const response = await apiClient.get<IServerResponse<ICountryHoliday[]>>(
|
||||||
|
`${rootUrl}/countries/${countryCode}`,
|
||||||
|
{ params }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
importCountryHolidays: async (
|
importCountryHolidays: async (
|
||||||
@@ -229,35 +221,35 @@ export const holidayApiService = {
|
|||||||
} as IServerResponse<IHolidayCalendarEvent[]>;
|
} as IServerResponse<IHolidayCalendarEvent[]>;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Organization holiday settings - PLACEHOLDER until backend implements
|
// Organization holiday settings
|
||||||
getOrganizationHolidaySettings: async (): Promise<
|
getOrganizationHolidaySettings: async (): Promise<
|
||||||
IServerResponse<IOrganizationHolidaySettings>
|
IServerResponse<IOrganizationHolidaySettings>
|
||||||
> => {
|
> => {
|
||||||
// Return default settings for now
|
const response = await apiClient.get<IServerResponse<IOrganizationHolidaySettings>>(
|
||||||
return {
|
`${API_BASE_URL}/admin-center/organization/holiday-settings`
|
||||||
done: true,
|
);
|
||||||
body: {
|
return response.data;
|
||||||
country_code: undefined,
|
|
||||||
state_code: undefined,
|
|
||||||
auto_sync_holidays: false,
|
|
||||||
},
|
|
||||||
} as IServerResponse<IOrganizationHolidaySettings>;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateOrganizationHolidaySettings: async (
|
updateOrganizationHolidaySettings: async (
|
||||||
data: IOrganizationHolidaySettings
|
data: IOrganizationHolidaySettings
|
||||||
): Promise<IServerResponse<any>> => {
|
): Promise<IServerResponse<any>> => {
|
||||||
// Just return success for now
|
const response = await apiClient.put<IServerResponse<any>>(
|
||||||
return {
|
`${API_BASE_URL}/admin-center/organization/holiday-settings`,
|
||||||
done: true,
|
data
|
||||||
body: {},
|
);
|
||||||
} as IServerResponse<any>;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Countries with states - PLACEHOLDER with date-holidays supported countries
|
// Countries with states
|
||||||
getCountriesWithStates: async (): Promise<IServerResponse<ICountryWithStates[]>> => {
|
getCountriesWithStates: async (): Promise<IServerResponse<ICountryWithStates[]>> => {
|
||||||
// Return comprehensive list of countries supported by date-holidays library
|
const response = await apiClient.get<IServerResponse<ICountryWithStates[]>>(
|
||||||
const supportedCountries = [
|
`${API_BASE_URL}/admin-center/countries-with-states`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
|
||||||
|
// Fallback to static data if API fails
|
||||||
|
/*const supportedCountries = [
|
||||||
{ code: 'AD', name: 'Andorra' },
|
{ code: 'AD', name: 'Andorra' },
|
||||||
{ code: 'AE', name: 'United Arab Emirates' },
|
{ code: 'AE', name: 'United Arab Emirates' },
|
||||||
{ code: 'AG', name: 'Antigua & Barbuda' },
|
{ code: 'AG', name: 'Antigua & Barbuda' },
|
||||||
@@ -696,41 +688,85 @@ export const holidayApiService = {
|
|||||||
return {
|
return {
|
||||||
done: true,
|
done: true,
|
||||||
body: supportedCountries,
|
body: supportedCountries,
|
||||||
} as IServerResponse<ICountryWithStates[]>;
|
} as IServerResponse<ICountryWithStates[]>;*/
|
||||||
},
|
},
|
||||||
|
|
||||||
// Combined holidays (official + custom) - Database-driven approach for Sri Lanka
|
// Combined holidays (official + custom) - Database-driven approach
|
||||||
getCombinedHolidays: async (
|
getCombinedHolidays: async (
|
||||||
params: ICombinedHolidaysRequest & { country_code?: string }
|
params: ICombinedHolidaysRequest & { country_code?: string }
|
||||||
): Promise<IServerResponse<IHolidayCalendarEvent[]>> => {
|
): Promise<IServerResponse<IHolidayCalendarEvent[]>> => {
|
||||||
try {
|
try {
|
||||||
|
console.log('🔍 getCombinedHolidays called with params:', params);
|
||||||
const year = new Date(params.from_date).getFullYear();
|
const year = new Date(params.from_date).getFullYear();
|
||||||
let allHolidays: IHolidayCalendarEvent[] = [];
|
let allHolidays: IHolidayCalendarEvent[] = [];
|
||||||
|
|
||||||
// Handle Sri Lankan holidays from database
|
// Get official holidays - handle Sri Lanka specially, others from API
|
||||||
if (params.country_code === 'LK' && year === 2025) {
|
if (params.country_code) {
|
||||||
// Import Sri Lankan holiday data
|
console.log(`🌐 Fetching official holidays for country: ${params.country_code}, year: ${year}`);
|
||||||
const { sriLankanHolidays2025 } = await import('@/data/sri-lanka-holidays-2025');
|
|
||||||
|
|
||||||
const sriLankanHolidays = sriLankanHolidays2025
|
// Handle Sri Lankan holidays from static data
|
||||||
.filter(h => h.date >= params.from_date && h.date <= params.to_date)
|
if (params.country_code === 'LK' && year === 2025) {
|
||||||
.map(h => ({
|
try {
|
||||||
id: `lk-${h.date}-${h.name.replace(/\s+/g, '-').toLowerCase()}`,
|
console.log('🇱🇰 Loading Sri Lankan holidays from static data...');
|
||||||
name: h.name,
|
const { sriLankanHolidays2025 } = await import('@/data/sri-lanka-holidays-2025');
|
||||||
description: h.description,
|
|
||||||
date: h.date,
|
const sriLankanHolidays = sriLankanHolidays2025
|
||||||
is_recurring: h.is_recurring,
|
.filter(h => h.date >= params.from_date && h.date <= params.to_date)
|
||||||
holiday_type_name: h.type,
|
.map(h => ({
|
||||||
color_code: h.color_code,
|
id: `lk-${h.date}-${h.name.replace(/\\s+/g, '-').toLowerCase()}`,
|
||||||
source: 'official' as const,
|
name: h.name,
|
||||||
is_editable: false,
|
description: h.description,
|
||||||
}));
|
date: h.date,
|
||||||
|
is_recurring: h.is_recurring,
|
||||||
allHolidays.push(...sriLankanHolidays);
|
holiday_type_name: h.type,
|
||||||
|
color_code: h.color_code,
|
||||||
|
source: 'official' as const,
|
||||||
|
is_editable: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log(`✅ Found ${sriLankanHolidays.length} Sri Lankan holidays`);
|
||||||
|
allHolidays.push(...sriLankanHolidays);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error loading Sri Lankan holidays:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle other countries from API
|
||||||
|
try {
|
||||||
|
const countryHolidaysRes = await holidayApiService.getCountryHolidays(params.country_code, year);
|
||||||
|
console.log('📅 Country holidays response:', countryHolidaysRes);
|
||||||
|
|
||||||
|
if (countryHolidaysRes.done && countryHolidaysRes.body) {
|
||||||
|
const officialHolidays = countryHolidaysRes.body
|
||||||
|
.filter((h: any) => h.date >= params.from_date && h.date <= params.to_date)
|
||||||
|
.map((h: any) => ({
|
||||||
|
id: `${params.country_code}-${h.id}`,
|
||||||
|
name: h.name,
|
||||||
|
description: h.description,
|
||||||
|
date: h.date,
|
||||||
|
is_recurring: h.is_recurring,
|
||||||
|
holiday_type_name: 'Official Holiday',
|
||||||
|
color_code: h.color_code || '#1890ff',
|
||||||
|
source: 'official' as const,
|
||||||
|
is_editable: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log(`✅ Found ${officialHolidays.length} official holidays from API`);
|
||||||
|
allHolidays.push(...officialHolidays);
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ No official holidays returned from API');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error fetching official holidays from API:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ No country code provided, skipping official holidays');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get organization holidays from database (includes both custom and country-specific)
|
// Get organization holidays from database (includes both custom and country-specific)
|
||||||
|
console.log(`🏢 Fetching organization holidays for year: ${year}`);
|
||||||
const customRes = await holidayApiService.getOrganizationHolidays(year);
|
const customRes = await holidayApiService.getOrganizationHolidays(year);
|
||||||
|
console.log('🏢 Organization holidays response:', customRes);
|
||||||
|
|
||||||
if (customRes.done && customRes.body) {
|
if (customRes.done && customRes.body) {
|
||||||
const customHolidays = customRes.body
|
const customHolidays = customRes.body
|
||||||
@@ -747,13 +783,17 @@ export const holidayApiService = {
|
|||||||
is_editable: h.is_editable !== false, // Default to true unless explicitly false
|
is_editable: h.is_editable !== false, // Default to true unless explicitly false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Filter out duplicates (in case Sri Lankan holidays are already in DB)
|
// Filter out duplicates (in case official holidays are already in DB)
|
||||||
const existingDates = new Set(allHolidays.map(h => h.date));
|
const existingDates = new Set(allHolidays.map(h => h.date));
|
||||||
const uniqueCustomHolidays = customHolidays.filter((h: any) => !existingDates.has(h.date));
|
const uniqueCustomHolidays = customHolidays.filter((h: any) => !existingDates.has(h.date));
|
||||||
|
|
||||||
|
console.log(`✅ Found ${customHolidays.length} organization holidays (${uniqueCustomHolidays.length} unique)`);
|
||||||
allHolidays.push(...uniqueCustomHolidays);
|
allHolidays.push(...uniqueCustomHolidays);
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ No organization holidays returned from API');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`🎉 Total holidays combined: ${allHolidays.length}`, allHolidays);
|
||||||
return {
|
return {
|
||||||
done: true,
|
done: true,
|
||||||
body: allHolidays,
|
body: allHolidays,
|
||||||
@@ -798,12 +838,9 @@ export const holidayApiService = {
|
|||||||
} as IServerResponse<{ working_days: number; total_days: number; holidays_count: number }>;
|
} as IServerResponse<{ working_days: number; total_days: number; holidays_count: number }>;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Populate holidays - PLACEHOLDER until backend implements (deprecated - keeping for backward compatibility)
|
// Populate holidays - Populate the database with official holidays for various countries
|
||||||
populateCountryHolidays: async (): Promise<IServerResponse<any>> => {
|
populateCountryHolidays: async (): Promise<IServerResponse<any>> => {
|
||||||
// Return success for now
|
const response = await apiClient.post<IServerResponse<any>>(`${rootUrl}/populate`);
|
||||||
return {
|
return response.data;
|
||||||
done: true,
|
|
||||||
body: { message: 'Holidays populated successfully' },
|
|
||||||
} as IServerResponse<any>;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,20 +4,37 @@
|
|||||||
|
|
||||||
.holiday-calendar .ant-picker-calendar {
|
.holiday-calendar .ant-picker-calendar {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .ant-picker-calendar {
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar .ant-picker-calendar-header {
|
.holiday-calendar .ant-picker-calendar-header {
|
||||||
padding: 12px 0;
|
padding: 20px 24px;
|
||||||
|
background: linear-gradient(135deg, #fafbfc 0%, #f1f3f4 100%);
|
||||||
|
border-bottom: 1px solid #e8eaed;
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .ant-picker-calendar-header {
|
||||||
|
background: linear-gradient(135deg, #1f1f1f 0%, #262626 100%);
|
||||||
|
border-bottom-color: #303030;
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar .ant-picker-calendar-date {
|
.holiday-calendar .ant-picker-calendar-date {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 80px;
|
height: 85px;
|
||||||
padding: 4px 8px;
|
padding: 6px 10px;
|
||||||
border: 1px solid #f0f0f0;
|
border: 1px solid #f0f0f0;
|
||||||
border-radius: 6px;
|
border-radius: 10px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar.dark .ant-picker-calendar-date {
|
.holiday-calendar.dark .ant-picker-calendar-date {
|
||||||
@@ -25,18 +42,161 @@
|
|||||||
background: #1f1f1f;
|
background: #1f1f1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Calendar cell wrapper */
|
||||||
|
.calendar-cell {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Working day styles */
|
||||||
|
.calendar-cell.working-day {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-cell.working-day::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(82, 196, 26, 0.04);
|
||||||
|
border-radius: inherit;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .calendar-cell.working-day {
|
||||||
|
background: #1f1f1f;
|
||||||
|
border: 1px solid #434343;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .calendar-cell.working-day::before {
|
||||||
|
background: rgba(82, 196, 26, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Non-working day styles */
|
||||||
|
.calendar-cell.non-working-day {
|
||||||
|
background: #fafafa;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .calendar-cell.non-working-day {
|
||||||
|
background: #141414;
|
||||||
|
border: 1px solid #303030;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Today styles */
|
||||||
|
.calendar-cell.today {
|
||||||
|
border: 2px solid #1890ff;
|
||||||
|
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.15), 0 4px 12px rgba(24, 144, 255, 0.1);
|
||||||
|
animation: todayPulse 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .calendar-cell.today {
|
||||||
|
border: 2px solid #177ddc;
|
||||||
|
box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Other month styles */
|
||||||
|
.calendar-cell.other-month {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .calendar-cell.other-month {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
.holiday-calendar .ant-picker-calendar-date:hover {
|
.holiday-calendar .ant-picker-calendar-date:hover {
|
||||||
background: #f5f5f5;
|
transform: translateY(-1px) scale(1.02);
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.04);
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: none) and (pointer: coarse) {
|
||||||
|
.holiday-calendar .ant-picker-calendar-date:hover {
|
||||||
|
transform: none;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
|
||||||
|
border-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-cell .ant-tag:hover {
|
||||||
|
transform: none;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar.dark .ant-picker-calendar-date:hover {
|
.holiday-calendar.dark .ant-picker-calendar-date:hover {
|
||||||
background: #2a2a2a;
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Working day indicator */
|
||||||
|
.working-day-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.working-day-badge {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #52c41a;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 1px 3px rgba(82, 196, 26, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 1px solid #d9f7be;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .working-day-badge {
|
||||||
|
background: #52c41a;
|
||||||
|
box-shadow: 0 1px 3px rgba(82, 196, 26, 0.4);
|
||||||
|
border-color: #237804;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.3);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes todayPulse {
|
||||||
|
0%, 100% {
|
||||||
|
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.15), 0 4px 12px rgba(24, 144, 255, 0.1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 4px rgba(24, 144, 255, 0.25), 0 6px 16px rgba(24, 144, 255, 0.15);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar .ant-picker-calendar-date-value {
|
.holiday-calendar .ant-picker-calendar-date-value {
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
color: #262626;
|
color: #1f1f1f;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar.dark .ant-picker-calendar-date-value {
|
.holiday-calendar.dark .ant-picker-calendar-date-value {
|
||||||
@@ -44,16 +204,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar .ant-picker-calendar-date-content {
|
.holiday-calendar .ant-picker-calendar-date-content {
|
||||||
height: 100%;
|
height: calc(100% - 20px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-cell {
|
.holiday-cell {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 2px;
|
bottom: 6px;
|
||||||
left: 2px;
|
left: 6px;
|
||||||
right: 2px;
|
right: 6px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
max-height: 65%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-cell .ant-tag {
|
.holiday-cell .ant-tag {
|
||||||
@@ -61,14 +227,27 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border: none;
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1.3;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-cell .ant-tag:hover {
|
.holiday-cell .ant-tag:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05) translateY(-1px);
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
border-color: rgba(255, 255, 255, 0.4);
|
||||||
|
z-index: 10;
|
||||||
|
overflow: visible;
|
||||||
|
white-space: normal;
|
||||||
|
text-overflow: unset;
|
||||||
|
max-width: 200px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar .ant-picker-calendar-date-today {
|
.holiday-calendar .ant-picker-calendar-date-today {
|
||||||
@@ -253,20 +432,243 @@
|
|||||||
|
|
||||||
/* Tag styles */
|
/* Tag styles */
|
||||||
.holiday-calendar .ant-tag {
|
.holiday-calendar .ant-tag {
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
line-height: 1.2;
|
line-height: 1.3;
|
||||||
padding: 1px 4px;
|
padding: 2px 6px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Holiday tag specific styles */
|
||||||
|
.holiday-tag.official-holiday {
|
||||||
|
background: #e6f7ff !important;
|
||||||
|
border-color: #91d5ff !important;
|
||||||
|
color: #1890ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-tag.custom-holiday {
|
||||||
|
background: #f6ffed !important;
|
||||||
|
border-color: #b7eb8f !important;
|
||||||
|
color: #52c41a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .holiday-tag.official-holiday {
|
||||||
|
background: #111b26 !important;
|
||||||
|
border-color: #13334c !important;
|
||||||
|
color: #69c0ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .holiday-tag.custom-holiday {
|
||||||
|
background: #162312 !important;
|
||||||
|
border-color: #274916 !important;
|
||||||
|
color: #95de64 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Holiday tag icons */
|
||||||
|
.custom-holiday-icon {
|
||||||
|
font-size: 8px;
|
||||||
|
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.official-holiday-icon {
|
||||||
|
font-size: 8px;
|
||||||
|
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendar container and legend */
|
||||||
|
.calendar-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-legend {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .calendar-legend {
|
||||||
|
background: #1f1f1f;
|
||||||
|
border-color: #434343;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .legend-item {
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot.working-day-dot {
|
||||||
|
background: #52c41a;
|
||||||
|
box-shadow: 0 0 0 1px rgba(82, 196, 26, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot.holiday-dot {
|
||||||
|
background: #ff4d4f;
|
||||||
|
box-shadow: 0 0 0 1px rgba(255, 77, 79, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot.today-dot {
|
||||||
|
background: #1890ff;
|
||||||
|
box-shadow: 0 0 0 1px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .legend-dot.working-day-dot {
|
||||||
|
background: #52c41a;
|
||||||
|
box-shadow: 0 0 0 1px rgba(82, 196, 26, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .legend-dot.holiday-dot {
|
||||||
|
background: #ff4d4f;
|
||||||
|
box-shadow: 0 0 0 1px rgba(255, 77, 79, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .legend-dot.today-dot {
|
||||||
|
background: #1890ff;
|
||||||
|
box-shadow: 0 0 0 1px rgba(24, 144, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legend badge and tag styles */
|
||||||
|
.legend-badge {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: white;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-badge.working-day-badge {
|
||||||
|
background: #52c41a;
|
||||||
|
border: 1px solid #d9f7be;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .legend-badge.working-day-badge {
|
||||||
|
background: #52c41a;
|
||||||
|
border: 1px solid #237804;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-tag {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-tag-text {
|
||||||
|
font-size: 8px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-holiday-legend {
|
||||||
|
background: #f6ffed;
|
||||||
|
border: 1px solid #b7eb8f;
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.official-holiday-legend {
|
||||||
|
background: #e6f7ff;
|
||||||
|
border: 1px solid #91d5ff;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .custom-holiday-legend {
|
||||||
|
background: #162312;
|
||||||
|
border: 1px solid #274916;
|
||||||
|
color: #95de64;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar.dark .official-holiday-legend {
|
||||||
|
background: #111b26;
|
||||||
|
border: 1px solid #13334c;
|
||||||
|
color: #69c0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-tag .custom-holiday-icon,
|
||||||
|
.legend-tag .official-holiday-icon {
|
||||||
|
font-size: 7px;
|
||||||
|
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive design */
|
/* Responsive design */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.holiday-calendar .ant-picker-calendar-date {
|
.holiday-calendar .ant-picker-calendar-date {
|
||||||
height: 60px;
|
height: 70px;
|
||||||
padding: 2px 4px;
|
padding: 4px 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar .ant-picker-calendar-date-value {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-cell .ant-tag {
|
||||||
|
font-size: 9px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-legend {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.working-day-badge {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar .ant-picker-calendar-header {
|
||||||
|
padding: 16px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holiday-calendar .ant-picker-calendar-date:hover {
|
||||||
|
transform: scale(1.01);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.holiday-calendar .ant-picker-calendar-date {
|
||||||
|
height: 65px;
|
||||||
|
padding: 3px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar .ant-picker-calendar-date-value {
|
.holiday-calendar .ant-picker-calendar-date-value {
|
||||||
@@ -274,7 +676,47 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.holiday-cell .ant-tag {
|
.holiday-cell .ant-tag {
|
||||||
font-size: 9px;
|
font-size: 8px;
|
||||||
padding: 0 2px;
|
padding: 1px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.working-day-badge {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
font-size: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-holiday-icon,
|
||||||
|
.official-holiday-icon {
|
||||||
|
font-size: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-legend {
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-badge {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-tag {
|
||||||
|
padding: 1px 4px;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-tag-text {
|
||||||
|
font-size: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-tag .custom-holiday-icon,
|
||||||
|
.legend-tag .official-holiday-icon {
|
||||||
|
font-size: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import {
|
|||||||
Tag,
|
Tag,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from '@/shared/antd-imports';
|
||||||
import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
import { PlusOutlined, DeleteOutlined, EditOutlined } from '@/shared/antd-imports';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { holidayApiService } from '@/api/holiday/holiday.api.service';
|
import { holidayApiService } from '@/api/holiday/holiday.api.service';
|
||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
import { RootState } from '@/app/store';
|
import { RootState } from '@/app/store';
|
||||||
import { fetchHolidays } from '@/features/admin-center/admin-center.slice';
|
import { fetchHolidays, clearHolidaysCache } from '@/features/admin-center/admin-center.slice';
|
||||||
import logger from '@/utils/errorLogger';
|
import logger from '@/utils/errorLogger';
|
||||||
import './holiday-calendar.css';
|
import './holiday-calendar.css';
|
||||||
|
|
||||||
@@ -38,9 +38,10 @@ const { TextArea } = Input;
|
|||||||
|
|
||||||
interface HolidayCalendarProps {
|
interface HolidayCalendarProps {
|
||||||
themeMode: string;
|
themeMode: string;
|
||||||
|
workingDays?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode }) => {
|
const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDays = [] }) => {
|
||||||
const { t } = useTranslation('admin-center/overview');
|
const { t } = useTranslation('admin-center/overview');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { holidays, loadingHolidays, holidaySettings } = useAppSelector(
|
const { holidays, loadingHolidays, holidaySettings } = useAppSelector(
|
||||||
@@ -66,10 +67,32 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchHolidaysForDateRange = () => {
|
const populateHolidaysIfNeeded = async () => {
|
||||||
|
// Check if we have holiday settings with a country code but no holidays
|
||||||
|
if (holidaySettings?.country_code && holidays.length === 0) {
|
||||||
|
try {
|
||||||
|
console.log('🔄 No holidays found, attempting to populate official holidays...');
|
||||||
|
const populateRes = await holidayApiService.populateCountryHolidays();
|
||||||
|
if (populateRes.done) {
|
||||||
|
console.log('✅ Official holidays populated successfully');
|
||||||
|
// Refresh holidays after population
|
||||||
|
fetchHolidaysForDateRange(true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Could not populate official holidays:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchHolidaysForDateRange = (forceRefresh = false) => {
|
||||||
const startOfYear = currentDate.startOf('year');
|
const startOfYear = currentDate.startOf('year');
|
||||||
const endOfYear = currentDate.endOf('year');
|
const endOfYear = currentDate.endOf('year');
|
||||||
|
|
||||||
|
// If forceRefresh is true, clear the cached holidays first
|
||||||
|
if (forceRefresh) {
|
||||||
|
dispatch(clearHolidaysCache());
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
fetchHolidays({
|
fetchHolidays({
|
||||||
from_date: startOfYear.format('YYYY-MM-DD'),
|
from_date: startOfYear.format('YYYY-MM-DD'),
|
||||||
@@ -84,6 +107,11 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode }) => {
|
|||||||
fetchHolidaysForDateRange();
|
fetchHolidaysForDateRange();
|
||||||
}, [currentDate.year()]);
|
}, [currentDate.year()]);
|
||||||
|
|
||||||
|
// Check if we need to populate holidays when holiday settings are loaded
|
||||||
|
useEffect(() => {
|
||||||
|
populateHolidaysIfNeeded();
|
||||||
|
}, [holidaySettings, holidays.length]);
|
||||||
|
|
||||||
const customHolidays = useMemo(() => {
|
const customHolidays = useMemo(() => {
|
||||||
return holidays.filter(holiday => holiday.source === 'custom');
|
return holidays.filter(holiday => holiday.source === 'custom');
|
||||||
}, [holidays]);
|
}, [holidays]);
|
||||||
@@ -103,7 +131,7 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode }) => {
|
|||||||
message.success(t('holidayCreated'));
|
message.success(t('holidayCreated'));
|
||||||
setModalVisible(false);
|
setModalVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
fetchHolidaysForDateRange();
|
fetchHolidaysForDateRange(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error creating holiday', error);
|
logger.error('Error creating holiday', error);
|
||||||
@@ -133,7 +161,7 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode }) => {
|
|||||||
setEditModalVisible(false);
|
setEditModalVisible(false);
|
||||||
editForm.resetFields();
|
editForm.resetFields();
|
||||||
setSelectedHoliday(null);
|
setSelectedHoliday(null);
|
||||||
fetchHolidaysForDateRange();
|
fetchHolidaysForDateRange(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error updating holiday', error);
|
logger.error('Error updating holiday', error);
|
||||||
@@ -146,7 +174,12 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode }) => {
|
|||||||
const res = await holidayApiService.deleteOrganizationHoliday(holidayId);
|
const res = await holidayApiService.deleteOrganizationHoliday(holidayId);
|
||||||
if (res.done) {
|
if (res.done) {
|
||||||
message.success(t('holidayDeleted'));
|
message.success(t('holidayDeleted'));
|
||||||
fetchHolidaysForDateRange();
|
// Close the edit modal and reset form
|
||||||
|
setEditModalVisible(false);
|
||||||
|
editForm.resetFields();
|
||||||
|
setSelectedHoliday(null);
|
||||||
|
// Refresh holidays data
|
||||||
|
fetchHolidaysForDateRange(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error deleting holiday', error);
|
logger.error('Error deleting holiday', error);
|
||||||
@@ -174,31 +207,55 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode }) => {
|
|||||||
|
|
||||||
const getHolidayDateCellRender = (date: Dayjs) => {
|
const getHolidayDateCellRender = (date: Dayjs) => {
|
||||||
const dateHolidays = holidays.filter(h => dayjs(h.date).isSame(date, 'day'));
|
const dateHolidays = holidays.filter(h => dayjs(h.date).isSame(date, 'day'));
|
||||||
|
const dayName = date.format('dddd');
|
||||||
|
// Check if this day is in the working days array from API response
|
||||||
|
const isWorkingDay = workingDays && workingDays.length > 0 ? workingDays.includes(dayName) : false;
|
||||||
|
const isToday = date.isSame(dayjs(), 'day');
|
||||||
|
const isCurrentMonth = date.isSame(currentDate, 'month');
|
||||||
|
|
||||||
if (dateHolidays.length > 0) {
|
return (
|
||||||
return (
|
<div className={`calendar-cell ${isWorkingDay ? 'working-day' : 'non-working-day'} ${isToday ? 'today' : ''} ${!isCurrentMonth ? 'other-month' : ''}`}>
|
||||||
<div className="holiday-cell">
|
{dateHolidays.length > 0 && (
|
||||||
{dateHolidays.map((holiday, index) => (
|
<div className="holiday-cell">
|
||||||
<Tag
|
{dateHolidays.map((holiday, index) => {
|
||||||
key={`${holiday.id}-${index}`}
|
const isOfficial = holiday.source === 'official';
|
||||||
color={holiday.color_code || (holiday.source === 'official' ? '#1890ff' : '#f37070')}
|
const isCustom = holiday.source === 'custom';
|
||||||
style={{
|
return (
|
||||||
fontSize: '10px',
|
<Tag
|
||||||
padding: '1px 4px',
|
key={`${holiday.id}-${index}`}
|
||||||
margin: '1px 0',
|
color={holiday.color_code || (isOfficial ? '#1890ff' : '#52c41a')}
|
||||||
borderRadius: '2px',
|
className={`holiday-tag ${isOfficial ? 'official-holiday' : 'custom-holiday'}`}
|
||||||
display: 'block',
|
style={{
|
||||||
opacity: holiday.source === 'official' ? 0.8 : 1,
|
fontSize: '10px',
|
||||||
}}
|
padding: '2px 6px',
|
||||||
title={`${holiday.name}${holiday.source === 'official' ? ' (Official)' : ' (Custom)'}`}
|
margin: '1px 0',
|
||||||
>
|
borderRadius: '4px',
|
||||||
{holiday.name}
|
display: 'block',
|
||||||
</Tag>
|
fontWeight: isCustom ? 600 : 500,
|
||||||
))}
|
border: isCustom ? '1px solid rgba(82, 196, 26, 0.6)' : '1px solid rgba(24, 144, 255, 0.4)',
|
||||||
</div>
|
position: 'relative',
|
||||||
);
|
}}
|
||||||
}
|
title={`${holiday.name}${isOfficial ? ' (Official Holiday)' : ' (Custom Holiday)'}`}
|
||||||
return null;
|
>
|
||||||
|
{isCustom && (
|
||||||
|
<span className="custom-holiday-icon" style={{ marginRight: '2px' }}>⭐</span>
|
||||||
|
)}
|
||||||
|
{isOfficial && (
|
||||||
|
<span className="official-holiday-icon" style={{ marginRight: '2px' }}>🏛️</span>
|
||||||
|
)}
|
||||||
|
{holiday.name}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isWorkingDay && (
|
||||||
|
<div className="working-day-indicator" title={`${dayName} - Working Day`}>
|
||||||
|
<div className="working-day-badge">W</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPanelChange = (value: Dayjs) => {
|
const onPanelChange = (value: Dayjs) => {
|
||||||
@@ -227,40 +284,83 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode }) => {
|
|||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'flex-start',
|
||||||
marginBottom: 16,
|
marginBottom: 18,
|
||||||
|
padding: '4px 0',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Title level={5} style={{ margin: 0 }}>
|
<div>
|
||||||
{t('holidayCalendar')}
|
<Title level={4} style={{ margin: '0 0 4px 0', fontWeight: 600 }}>
|
||||||
</Title>
|
{t('holidayCalendar')}
|
||||||
<Space>
|
</Title>
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={() => setModalVisible(true)}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{t('addCustomHoliday') || 'Add Custom Holiday'}
|
|
||||||
</Button>
|
|
||||||
{holidaySettings?.country_code && (
|
{holidaySettings?.country_code && (
|
||||||
<Typography.Text type="secondary" style={{ fontSize: '12px' }}>
|
<Typography.Text type="secondary" style={{ fontSize: '13px', fontWeight: 500 }}>
|
||||||
{t('officialHolidaysFrom') || 'Official holidays from'}:{' '}
|
{t('officialHolidaysFrom') || 'Official holidays from'}:{' '}
|
||||||
{holidaySettings.country_code}
|
<span style={{ color: themeMode === 'dark' ? '#40a9ff' : '#1890ff' }}>
|
||||||
{holidaySettings.state_code && ` (${holidaySettings.state_code})`}
|
{holidaySettings.country_code}
|
||||||
|
{holidaySettings.state_code && ` (${holidaySettings.state_code})`}
|
||||||
|
</span>
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => setModalVisible(true)}
|
||||||
|
style={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontWeight: 500,
|
||||||
|
boxShadow: '0 2px 8px rgba(24, 144, 255, 0.2)',
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||||
|
e.currentTarget.style.boxShadow = '0 4px 12px rgba(24, 144, 255, 0.3)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.transform = 'translateY(0)';
|
||||||
|
e.currentTarget.style.boxShadow = '0 2px 8px rgba(24, 144, 255, 0.2)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('addCustomHoliday') || 'Add Custom Holiday'}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Calendar
|
<div className="calendar-container">
|
||||||
value={currentDate}
|
<Calendar
|
||||||
onPanelChange={onPanelChange}
|
value={currentDate}
|
||||||
onSelect={onDateSelect}
|
onPanelChange={onPanelChange}
|
||||||
dateCellRender={getHolidayDateCellRender}
|
onSelect={onDateSelect}
|
||||||
className={`holiday-calendar ${themeMode}`}
|
dateCellRender={getHolidayDateCellRender}
|
||||||
loading={loadingHolidays}
|
className={`holiday-calendar ${themeMode}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Calendar Legend */}
|
||||||
|
<div className="calendar-legend">
|
||||||
|
<div className="legend-item">
|
||||||
|
<div className="legend-badge working-day-badge">W</div>
|
||||||
|
<span>{t('workingDay') || 'Working Day'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="legend-item">
|
||||||
|
<div className="legend-tag custom-holiday-legend">
|
||||||
|
<span className="custom-holiday-icon">⭐</span>
|
||||||
|
<span className="legend-tag-text">Custom</span>
|
||||||
|
</div>
|
||||||
|
<span>{t('customHoliday') || 'Custom Holiday'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="legend-item">
|
||||||
|
<div className="legend-tag official-holiday-legend">
|
||||||
|
<span className="official-holiday-icon">🏛️</span>
|
||||||
|
<span className="legend-tag-text">Official</span>
|
||||||
|
</div>
|
||||||
|
<span>{t('officialHoliday') || 'Official Holiday'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="legend-item">
|
||||||
|
<div className="legend-dot today-dot"></div>
|
||||||
|
<span>{t('today') || 'Today'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Create Holiday Modal */}
|
{/* Create Holiday Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
@@ -417,6 +517,18 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode }) => {
|
|||||||
>
|
>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
{selectedHoliday && selectedHoliday.source === 'custom' && selectedHoliday.is_editable && (
|
||||||
|
<Popconfirm
|
||||||
|
title={t('deleteHolidayConfirm') || 'Are you sure you want to delete this holiday?'}
|
||||||
|
onConfirm={() => handleDeleteHoliday(selectedHoliday.id)}
|
||||||
|
okText={t('yes') || 'Yes'}
|
||||||
|
cancelText={t('no') || 'No'}
|
||||||
|
>
|
||||||
|
<Button type="primary" danger icon={<DeleteOutlined />}>
|
||||||
|
{t('delete') || 'Delete'}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -20,16 +20,6 @@ export interface SriLankanHolidayData {
|
|||||||
|
|
||||||
export const sriLankanHolidays2025: SriLankanHolidayData[] = [
|
export const sriLankanHolidays2025: SriLankanHolidayData[] = [
|
||||||
// January
|
// January
|
||||||
{
|
|
||||||
name: "New Year's Day",
|
|
||||||
date: "2025-01-01",
|
|
||||||
type: "Public",
|
|
||||||
description: "Celebration of the first day of the Gregorian calendar year",
|
|
||||||
is_recurring: true,
|
|
||||||
is_poya: false,
|
|
||||||
country_code: "LK",
|
|
||||||
color_code: "#DC143C"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Duruthu Full Moon Poya Day",
|
name: "Duruthu Full Moon Poya Day",
|
||||||
date: "2025-01-13",
|
date: "2025-01-13",
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ export const updateHolidaySettings = createAsyncThunk(
|
|||||||
async (settings: IOrganizationHolidaySettings) => {
|
async (settings: IOrganizationHolidaySettings) => {
|
||||||
const { holidayApiService } = await import('@/api/holiday/holiday.api.service');
|
const { holidayApiService } = await import('@/api/holiday/holiday.api.service');
|
||||||
await holidayApiService.updateOrganizationHolidaySettings(settings);
|
await holidayApiService.updateOrganizationHolidaySettings(settings);
|
||||||
await adminCenterApiService.updateOrganizationHolidaySettings(settings);
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -164,6 +163,10 @@ const adminCenterSlice = createSlice({
|
|||||||
? (state.isUpgradeModalOpen = false)
|
? (state.isUpgradeModalOpen = false)
|
||||||
: (state.isUpgradeModalOpen = true);
|
: (state.isUpgradeModalOpen = true);
|
||||||
},
|
},
|
||||||
|
clearHolidaysCache: state => {
|
||||||
|
state.holidays = [];
|
||||||
|
state.holidaysDateRange = null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: builder => {
|
extraReducers: builder => {
|
||||||
builder.addCase(fetchBillingInfo.pending, (state, action) => {
|
builder.addCase(fetchBillingInfo.pending, (state, action) => {
|
||||||
@@ -264,7 +267,7 @@ const adminCenterSlice = createSlice({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { toggleRedeemCodeDrawer, toggleUpgradeModal } = adminCenterSlice.actions;
|
export const { toggleRedeemCodeDrawer, toggleUpgradeModal, clearHolidaysCache } = adminCenterSlice.actions;
|
||||||
|
|
||||||
// Selectors for optimized access
|
// Selectors for optimized access
|
||||||
export const selectHolidaysByDateRange = createSelector(
|
export const selectHolidaysByDateRange = createSelector(
|
||||||
|
|||||||
@@ -12,17 +12,14 @@ import {
|
|||||||
message,
|
message,
|
||||||
Select,
|
Select,
|
||||||
Switch,
|
Switch,
|
||||||
Divider,
|
|
||||||
} from '@/shared/antd-imports';
|
} from '@/shared/antd-imports';
|
||||||
import { PageHeader } from '@ant-design/pro-components';
|
import { PageHeader } from '@ant-design/pro-components';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { IOrganization, IOrganizationAdmin } from '@/types/admin-center/admin-center.types';
|
|
||||||
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 { 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';
|
||||||
import { holidayApiService } from '@/api/holiday/holiday.api.service';
|
|
||||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
import { RootState } from '@/app/store';
|
import { RootState } from '@/app/store';
|
||||||
@@ -38,11 +35,7 @@ const SettingsPage: React.FC = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const {
|
const {
|
||||||
organization,
|
organization,
|
||||||
organizationAdmins,
|
|
||||||
loadingOrganization,
|
|
||||||
loadingOrganizationAdmins,
|
|
||||||
holidaySettings,
|
holidaySettings,
|
||||||
loadingHolidaySettings,
|
|
||||||
countriesWithStates,
|
countriesWithStates,
|
||||||
loadingCountries,
|
loadingCountries,
|
||||||
} = useAppSelector((state: RootState) => state.adminCenterReducer);
|
} = useAppSelector((state: RootState) => state.adminCenterReducer);
|
||||||
@@ -241,7 +234,7 @@ const SettingsPage: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
showSearch
|
showSearch
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
(option?.children as string)?.toLowerCase().includes(input.toLowerCase())
|
(option?.children as unknown as string)?.toLowerCase().includes(input.toLowerCase())
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{countriesWithStates.map(country => (
|
{countriesWithStates.map(country => (
|
||||||
@@ -260,7 +253,7 @@ const SettingsPage: React.FC = () => {
|
|||||||
disabled={!holidayForm.getFieldValue('country_code')}
|
disabled={!holidayForm.getFieldValue('country_code')}
|
||||||
showSearch
|
showSearch
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
(option?.children as string)?.toLowerCase().includes(input.toLowerCase())
|
(option?.children as unknown as string)?.toLowerCase().includes(input.toLowerCase())
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{getSelectedCountryStates().map(state => (
|
{getSelectedCountryStates().map(state => (
|
||||||
@@ -287,7 +280,7 @@ const SettingsPage: React.FC = () => {
|
|||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<HolidayCalendar themeMode={themeMode} />
|
<HolidayCalendar themeMode={themeMode} workingDays={workingDays} />
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user