Merge branch 'feature/add-calender-and-holidays' of https://github.com/Worklenz/worklenz into feature/team-utilization

This commit is contained in:
chamiakJ
2025-07-26 12:31:35 +05:30
17 changed files with 2209 additions and 444 deletions

View File

@@ -28,5 +28,33 @@
"manDaysCalculationDescription": "All project costs will be calculated using estimated man days × daily rates",
"calculationMethodTooltip": "This setting applies to all projects in your organization",
"calculationMethodUpdated": "Organization calculation method updated successfully",
"calculationMethodUpdateError": "Failed to update calculation method"
"calculationMethodUpdateError": "Failed to update calculation method",
"holidayCalendar": "Holiday Calendar",
"addHoliday": "Add Holiday",
"editHoliday": "Edit Holiday",
"holidayName": "Holiday Name",
"holidayNameRequired": "Please enter holiday name",
"description": "Description",
"date": "Date",
"dateRequired": "Please select a date",
"holidayType": "Holiday Type",
"holidayTypeRequired": "Please select a holiday type",
"recurring": "Recurring",
"save": "Save",
"update": "Update",
"cancel": "Cancel",
"holidayCreated": "Holiday created successfully",
"holidayUpdated": "Holiday updated successfully",
"holidayDeleted": "Holiday deleted successfully",
"errorCreatingHoliday": "Error creating holiday",
"errorUpdatingHoliday": "Error updating holiday",
"errorDeletingHoliday": "Error deleting holiday",
"importCountryHolidays": "Import Country Holidays",
"country": "Country",
"countryRequired": "Please select a country",
"selectCountry": "Select a country",
"year": "Year",
"import": "Import",
"holidaysImported": "Successfully imported {{count}} holidays",
"errorImportingHolidays": "Error importing holidays"
}

View File

@@ -0,0 +1,74 @@
import { API_BASE_URL } from '@/shared/constants';
import apiClient from '../api-client';
import { IServerResponse } from '@/types/common.types';
import {
IHolidayType,
IOrganizationHoliday,
ICountryHoliday,
IAvailableCountry,
ICreateHolidayRequest,
IUpdateHolidayRequest,
IImportCountryHolidaysRequest,
IHolidayCalendarEvent,
} from '@/types/holiday/holiday.types';
const rootUrl = `${API_BASE_URL}/holidays`;
export const holidayApiService = {
// Holiday types
getHolidayTypes: async (): Promise<IServerResponse<IHolidayType[]>> => {
const response = await apiClient.get<IServerResponse<IHolidayType[]>>(`${rootUrl}/types`);
return response.data;
},
// Organization holidays
getOrganizationHolidays: async (year?: number): Promise<IServerResponse<IOrganizationHoliday[]>> => {
const params = year ? `?year=${year}` : '';
const response = await apiClient.get<IServerResponse<IOrganizationHoliday[]>>(`${rootUrl}/organization${params}`);
return response.data;
},
createOrganizationHoliday: async (data: ICreateHolidayRequest): Promise<IServerResponse<any>> => {
const response = await apiClient.post<IServerResponse<any>>(`${rootUrl}/organization`, data);
return response.data;
},
updateOrganizationHoliday: async (id: string, data: IUpdateHolidayRequest): Promise<IServerResponse<any>> => {
const response = await apiClient.put<IServerResponse<any>>(`${rootUrl}/organization/${id}`, data);
return response.data;
},
deleteOrganizationHoliday: async (id: string): Promise<IServerResponse<any>> => {
const response = await apiClient.delete<IServerResponse<any>>(`${rootUrl}/organization/${id}`);
return response.data;
},
// Country holidays
getAvailableCountries: async (): Promise<IServerResponse<IAvailableCountry[]>> => {
const response = await apiClient.get<IServerResponse<IAvailableCountry[]>>(`${rootUrl}/countries`);
return response.data;
},
getCountryHolidays: async (countryCode: string, year?: number): Promise<IServerResponse<ICountryHoliday[]>> => {
const params = year ? `?year=${year}` : '';
const response = await apiClient.get<IServerResponse<ICountryHoliday[]>>(`${rootUrl}/countries/${countryCode}${params}`);
return response.data;
},
importCountryHolidays: async (data: IImportCountryHolidaysRequest): Promise<IServerResponse<any>> => {
const response = await apiClient.post<IServerResponse<any>>(`${rootUrl}/import`, data);
return response.data;
},
// Calendar view
getHolidayCalendar: async (year: number, month: number): Promise<IServerResponse<IHolidayCalendarEvent[]>> => {
const response = await apiClient.get<IServerResponse<IHolidayCalendarEvent[]>>(`${rootUrl}/calendar?year=${year}&month=${month}`);
return response.data;
},
// Populate holidays
populateCountryHolidays: async (): Promise<IServerResponse<any>> => {
const response = await apiClient.post<IServerResponse<any>>(`${rootUrl}/populate`);
return response.data;
},
};

View File

@@ -0,0 +1,270 @@
.holiday-calendar {
width: 100%;
}
.holiday-calendar .ant-picker-calendar {
background: transparent;
}
.holiday-calendar .ant-picker-calendar-header {
padding: 12px 0;
}
.holiday-calendar .ant-picker-calendar-date {
position: relative;
height: 80px;
padding: 4px 8px;
border: 1px solid #f0f0f0;
border-radius: 6px;
transition: all 0.3s;
}
.holiday-calendar.dark .ant-picker-calendar-date {
border-color: #303030;
background: #1f1f1f;
}
.holiday-calendar .ant-picker-calendar-date:hover {
background: #f5f5f5;
}
.holiday-calendar.dark .ant-picker-calendar-date:hover {
background: #2a2a2a;
}
.holiday-calendar .ant-picker-calendar-date-value {
font-size: 12px;
font-weight: 500;
color: #262626;
}
.holiday-calendar.dark .ant-picker-calendar-date-value {
color: #ffffff;
}
.holiday-calendar .ant-picker-calendar-date-content {
height: 100%;
overflow: hidden;
}
.holiday-cell {
position: absolute;
bottom: 2px;
left: 2px;
right: 2px;
z-index: 1;
}
.holiday-cell .ant-tag {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border: none;
font-weight: 500;
}
.holiday-calendar .ant-picker-calendar-date-today {
border-color: #1890ff;
background: #e6f7ff;
}
.holiday-calendar.dark .ant-picker-calendar-date-today {
border-color: #177ddc;
background: #111b26;
}
.holiday-calendar .ant-picker-calendar-date-selected {
border-color: #1890ff;
background: #e6f7ff;
}
.holiday-calendar.dark .ant-picker-calendar-date-selected {
border-color: #177ddc;
background: #111b26;
}
.holiday-calendar .ant-picker-calendar-date-other {
color: #bfbfbf;
}
.holiday-calendar.dark .ant-picker-calendar-date-other {
color: #595959;
}
.holiday-calendar .ant-picker-calendar-date-other .ant-picker-calendar-date-value {
color: #bfbfbf;
}
.holiday-calendar.dark .ant-picker-calendar-date-other .ant-picker-calendar-date-value {
color: #595959;
}
.holiday-calendar .ant-picker-calendar-mini {
border: 1px solid #f0f0f0;
border-radius: 6px;
}
.holiday-calendar.dark .ant-picker-calendar-mini {
border-color: #303030;
background: #1f1f1f;
}
.holiday-calendar .ant-picker-calendar-mini .ant-picker-calendar-date {
border: none;
border-radius: 0;
}
.holiday-calendar .ant-picker-calendar-mini .ant-picker-calendar-date:hover {
background: #f5f5f5;
}
.holiday-calendar.dark .ant-picker-calendar-mini .ant-picker-calendar-date:hover {
background: #2a2a2a;
}
/* Modal styles */
.holiday-calendar .ant-modal-content {
border-radius: 8px;
}
.holiday-calendar.dark .ant-modal-content {
background: #1f1f1f;
border: 1px solid #303030;
}
.holiday-calendar.dark .ant-modal-header {
background: #1f1f1f;
border-bottom: 1px solid #303030;
}
.holiday-calendar.dark .ant-modal-title {
color: #ffffff;
}
.holiday-calendar.dark .ant-modal-close {
color: #ffffff;
}
.holiday-calendar.dark .ant-form-item-label > label {
color: #ffffff;
}
.holiday-calendar.dark .ant-input,
.holiday-calendar.dark .ant-input-textarea {
background: #2a2a2a;
border-color: #434343;
color: #ffffff;
}
.holiday-calendar.dark .ant-input:focus,
.holiday-calendar.dark .ant-input-textarea:focus {
border-color: #177ddc;
box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2);
}
.holiday-calendar.dark .ant-select-selector {
background: #2a2a2a !important;
border-color: #434343 !important;
color: #ffffff !important;
}
.holiday-calendar.dark .ant-select-focused .ant-select-selector {
border-color: #177ddc !important;
box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2) !important;
}
.holiday-calendar.dark .ant-picker {
background: #2a2a2a;
border-color: #434343;
}
.holiday-calendar.dark .ant-picker-input > input {
color: #ffffff;
}
.holiday-calendar.dark .ant-picker-focused {
border-color: #177ddc;
box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2);
}
/* Button styles */
.holiday-calendar .ant-btn {
border-radius: 6px;
font-weight: 500;
}
.holiday-calendar.dark .ant-btn {
border-color: #434343;
}
.holiday-calendar.dark .ant-btn-primary {
background: #177ddc;
border-color: #177ddc;
}
.holiday-calendar.dark .ant-btn-primary:hover {
background: #1890ff;
border-color: #1890ff;
}
/* Card styles */
.holiday-calendar .ant-card {
border-radius: 8px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02), 0 2px 4px 0 rgba(0, 0, 0, 0.02);
}
.holiday-calendar.dark .ant-card {
background: #1f1f1f;
border-color: #303030;
}
.holiday-calendar.dark .ant-card-head {
border-bottom-color: #303030;
}
.holiday-calendar.dark .ant-card-head-title {
color: #ffffff;
}
/* Typography styles */
.holiday-calendar.dark .ant-typography {
color: #ffffff;
}
.holiday-calendar.dark .ant-typography h5 {
color: #ffffff;
}
/* Space styles */
.holiday-calendar .ant-space {
gap: 8px !important;
}
/* Tag styles */
.holiday-calendar .ant-tag {
border-radius: 4px;
font-size: 10px;
line-height: 1.2;
padding: 1px 4px;
margin: 0;
border: none;
font-weight: 500;
}
/* Responsive design */
@media (max-width: 768px) {
.holiday-calendar .ant-picker-calendar-date {
height: 60px;
padding: 2px 4px;
}
.holiday-calendar .ant-picker-calendar-date-value {
font-size: 11px;
}
.holiday-cell .ant-tag {
font-size: 9px;
padding: 0 2px;
}
}

View File

@@ -0,0 +1,433 @@
import React, { useEffect, useState } from 'react';
import { Calendar, Card, Typography, Button, Modal, Form, Input, Select, DatePicker, Switch, Space, Tag, Popconfirm, message } from 'antd';
import { PlusOutlined, DeleteOutlined, EditOutlined, GlobalOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import dayjs, { Dayjs } from 'dayjs';
import { holidayApiService } from '@/api/holiday/holiday.api.service';
import {
IHolidayType,
IOrganizationHoliday,
IAvailableCountry,
ICreateHolidayRequest,
IUpdateHolidayRequest,
} from '@/types/holiday/holiday.types';
import logger from '@/utils/errorLogger';
import './holiday-calendar.css';
const { Title, Text } = Typography;
const { Option } = Select;
const { TextArea } = Input;
interface HolidayCalendarProps {
themeMode: string;
}
const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode }) => {
const { t } = useTranslation('admin-center/overview');
const [form] = Form.useForm();
const [editForm] = Form.useForm();
const [holidayTypes, setHolidayTypes] = useState<IHolidayType[]>([]);
const [organizationHolidays, setOrganizationHolidays] = useState<IOrganizationHoliday[]>([]);
const [availableCountries, setAvailableCountries] = useState<IAvailableCountry[]>([]);
const [loading, setLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [editModalVisible, setEditModalVisible] = useState(false);
const [importModalVisible, setImportModalVisible] = useState(false);
const [selectedHoliday, setSelectedHoliday] = useState<IOrganizationHoliday | null>(null);
const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs());
const fetchHolidayTypes = async () => {
try {
const res = await holidayApiService.getHolidayTypes();
if (res.done) {
setHolidayTypes(res.body);
}
} catch (error) {
logger.error('Error fetching holiday types', error);
}
};
const fetchOrganizationHolidays = async () => {
setLoading(true);
try {
const res = await holidayApiService.getOrganizationHolidays(currentDate.year());
if (res.done) {
setOrganizationHolidays(res.body);
}
} catch (error) {
logger.error('Error fetching organization holidays', error);
} finally {
setLoading(false);
}
};
const fetchAvailableCountries = async () => {
try {
const res = await holidayApiService.getAvailableCountries();
if (res.done) {
setAvailableCountries(res.body);
}
} catch (error) {
logger.error('Error fetching available countries', error);
}
};
useEffect(() => {
fetchHolidayTypes();
fetchOrganizationHolidays();
fetchAvailableCountries();
}, [currentDate.year()]);
const handleCreateHoliday = async (values: any) => {
try {
const holidayData: ICreateHolidayRequest = {
name: values.name,
description: values.description,
date: values.date.format('YYYY-MM-DD'),
holiday_type_id: values.holiday_type_id,
is_recurring: values.is_recurring || false,
};
const res = await holidayApiService.createOrganizationHoliday(holidayData);
if (res.done) {
message.success(t('holidayCreated'));
setModalVisible(false);
form.resetFields();
fetchOrganizationHolidays();
}
} catch (error) {
logger.error('Error creating holiday', error);
message.error(t('errorCreatingHoliday'));
}
};
const handleUpdateHoliday = async (values: any) => {
if (!selectedHoliday) return;
try {
const holidayData: IUpdateHolidayRequest = {
id: selectedHoliday.id,
name: values.name,
description: values.description,
date: values.date?.format('YYYY-MM-DD'),
holiday_type_id: values.holiday_type_id,
is_recurring: values.is_recurring,
};
const res = await holidayApiService.updateOrganizationHoliday(selectedHoliday.id, holidayData);
if (res.done) {
message.success(t('holidayUpdated'));
setEditModalVisible(false);
editForm.resetFields();
setSelectedHoliday(null);
fetchOrganizationHolidays();
}
} catch (error) {
logger.error('Error updating holiday', error);
message.error(t('errorUpdatingHoliday'));
}
};
const handleDeleteHoliday = async (holidayId: string) => {
try {
const res = await holidayApiService.deleteOrganizationHoliday(holidayId);
if (res.done) {
message.success(t('holidayDeleted'));
fetchOrganizationHolidays();
}
} catch (error) {
logger.error('Error deleting holiday', error);
message.error(t('errorDeletingHoliday'));
}
};
const handleImportCountryHolidays = async (values: any) => {
try {
const res = await holidayApiService.importCountryHolidays({
country_code: values.country_code,
year: values.year || currentDate.year(),
});
if (res.done) {
message.success(t('holidaysImported', { count: res.body.imported_count }));
setImportModalVisible(false);
fetchOrganizationHolidays();
}
} catch (error) {
logger.error('Error importing country holidays', error);
message.error(t('errorImportingHolidays'));
}
};
const handleEditHoliday = (holiday: IOrganizationHoliday) => {
setSelectedHoliday(holiday);
editForm.setFieldsValue({
name: holiday.name,
description: holiday.description,
date: dayjs(holiday.date),
holiday_type_id: holiday.holiday_type_id,
is_recurring: holiday.is_recurring,
});
setEditModalVisible(true);
};
const getHolidayDateCellRender = (date: Dayjs) => {
const holiday = organizationHolidays.find(h => dayjs(h.date).isSame(date, 'day'));
if (holiday) {
const holidayType = holidayTypes.find(ht => ht.id === holiday.holiday_type_id);
return (
<div className="holiday-cell">
<Tag
color={holidayType?.color_code || '#f37070'}
style={{
fontSize: '10px',
padding: '1px 4px',
margin: 0,
borderRadius: '2px'
}}
>
{holiday.name}
</Tag>
</div>
);
}
return null;
};
const onPanelChange = (value: Dayjs) => {
setCurrentDate(value);
};
return (
<Card>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<Title level={5} style={{ margin: 0 }}>
{t('holidayCalendar')}
</Title>
<Space>
<Button
icon={<GlobalOutlined />}
onClick={() => setImportModalVisible(true)}
size="small"
>
{t('importCountryHolidays')}
</Button>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setModalVisible(true)}
size="small"
>
{t('addHoliday')}
</Button>
</Space>
</div>
<Calendar
value={currentDate}
onPanelChange={onPanelChange}
dateCellRender={getHolidayDateCellRender}
className={`holiday-calendar ${themeMode}`}
/>
{/* Create Holiday Modal */}
<Modal
title={t('addHoliday')}
open={modalVisible}
onCancel={() => {
setModalVisible(false);
form.resetFields();
}}
footer={null}
destroyOnClose
>
<Form form={form} layout="vertical" onFinish={handleCreateHoliday}>
<Form.Item
name="name"
label={t('holidayName')}
rules={[{ required: true, message: t('holidayNameRequired') }]}
>
<Input />
</Form.Item>
<Form.Item name="description" label={t('description')}>
<TextArea rows={3} />
</Form.Item>
<Form.Item
name="date"
label={t('date')}
rules={[{ required: true, message: t('dateRequired') }]}
>
<DatePicker style={{ width: '100%' }} />
</Form.Item>
<Form.Item
name="holiday_type_id"
label={t('holidayType')}
rules={[{ required: true, message: t('holidayTypeRequired') }]}
>
<Select>
{holidayTypes.map(type => (
<Option key={type.id} value={type.id}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div
style={{
width: 12,
height: 12,
borderRadius: '50%',
backgroundColor: type.color_code,
marginRight: 8
}}
/>
{type.name}
</div>
</Option>
))}
</Select>
</Form.Item>
<Form.Item name="is_recurring" label={t('recurring')} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit">
{t('save')}
</Button>
<Button onClick={() => {
setModalVisible(false);
form.resetFields();
}}>
{t('cancel')}
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
{/* Edit Holiday Modal */}
<Modal
title={t('editHoliday')}
open={editModalVisible}
onCancel={() => {
setEditModalVisible(false);
editForm.resetFields();
setSelectedHoliday(null);
}}
footer={null}
destroyOnClose
>
<Form form={editForm} layout="vertical" onFinish={handleUpdateHoliday}>
<Form.Item
name="name"
label={t('holidayName')}
rules={[{ required: true, message: t('holidayNameRequired') }]}
>
<Input />
</Form.Item>
<Form.Item name="description" label={t('description')}>
<TextArea rows={3} />
</Form.Item>
<Form.Item
name="date"
label={t('date')}
rules={[{ required: true, message: t('dateRequired') }]}
>
<DatePicker style={{ width: '100%' }} />
</Form.Item>
<Form.Item
name="holiday_type_id"
label={t('holidayType')}
rules={[{ required: true, message: t('holidayTypeRequired') }]}
>
<Select>
{holidayTypes.map(type => (
<Option key={type.id} value={type.id}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div
style={{
width: 12,
height: 12,
borderRadius: '50%',
backgroundColor: type.color_code,
marginRight: 8
}}
/>
{type.name}
</div>
</Option>
))}
</Select>
</Form.Item>
<Form.Item name="is_recurring" label={t('recurring')} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit">
{t('update')}
</Button>
<Button onClick={() => {
setEditModalVisible(false);
editForm.resetFields();
setSelectedHoliday(null);
}}>
{t('cancel')}
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
{/* Import Country Holidays Modal */}
<Modal
title={t('importCountryHolidays')}
open={importModalVisible}
onCancel={() => setImportModalVisible(false)}
footer={null}
destroyOnClose
>
<Form layout="vertical" onFinish={handleImportCountryHolidays}>
<Form.Item
name="country_code"
label={t('country')}
rules={[{ required: true, message: t('countryRequired') }]}
>
<Select placeholder={t('selectCountry')}>
{availableCountries.map(country => (
<Option key={country.code} value={country.code}>
{country.name}
</Option>
))}
</Select>
</Form.Item>
<Form.Item name="year" label={t('year')}>
<DatePicker picker="year" style={{ width: '100%' }} />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit">
{t('import')}
</Button>
<Button onClick={() => setImportModalVisible(false)}>
{t('cancel')}
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
</Card>
);
};
export default HolidayCalendar;

View File

@@ -1,6 +1,9 @@
import React, { useEffect, useState } from 'react';
import {
Button,
Card,
DatabaseOutlined,
message,
Space,
Typography,
} from '@/shared/antd-imports';
@@ -11,7 +14,9 @@ import { RootState } from '@/app/store';
import { useTranslation } from 'react-i18next';
import OrganizationName from '@/components/admin-center/overview/organization-name/organization-name';
import OrganizationOwner from '@/components/admin-center/overview/organization-owner/organization-owner';
import HolidayCalendar from '@/components/admin-center/overview/holiday-calendar/holiday-calendar';
import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service';
import { holidayApiService } from '@/api/holiday/holiday.api.service';
import { IOrganization, IOrganizationAdmin } from '@/types/admin-center/admin-center.types';
import logger from '@/utils/errorLogger';
@@ -19,6 +24,7 @@ const Overview: React.FC = () => {
const [organization, setOrganization] = useState<IOrganization | null>(null);
const [organizationAdmins, setOrganizationAdmins] = useState<IOrganizationAdmin[] | null>(null);
const [loadingAdmins, setLoadingAdmins] = useState(false);
const [populatingHolidays, setPopulatingHolidays] = useState(false);
const themeMode = useAppSelector((state: RootState) => state.themeReducer.mode);
const { t } = useTranslation('admin-center/overview');
@@ -48,6 +54,21 @@ const Overview: React.FC = () => {
}
};
const handlePopulateHolidays = async () => {
setPopulatingHolidays(true);
try {
const res = await holidayApiService.populateCountryHolidays();
if (res.done) {
message.success(`Successfully populated ${res.body.total_populated} holidays`);
}
} catch (error) {
logger.error('Error populating holidays', error);
message.error('Failed to populate holidays');
} finally {
setPopulatingHolidays(false);
}
};
useEffect(() => {
getOrganizationDetails();
getOrganizationAdmins();
@@ -55,7 +76,20 @@ const Overview: React.FC = () => {
return (
<div style={{ width: '100%' }}>
<PageHeader title={<span>{t('overview')}</span>} style={{ padding: '16px 0' }} />
<PageHeader
title={<span>{t('overview')}</span>}
style={{ padding: '16px 0' }}
extra={[
<Button
key="populate-holidays"
icon={<DatabaseOutlined />}
onClick={handlePopulateHolidays}
loading={populatingHolidays}
>
Populate Holidays Database
</Button>
]}
/>
<Space direction="vertical" style={{ width: '100%' }} size={22}>
<OrganizationName
@@ -82,6 +116,8 @@ const Overview: React.FC = () => {
themeMode={themeMode}
/>
</Card>
<HolidayCalendar themeMode={themeMode} />
</Space>
</div>
);

View File

@@ -186,7 +186,8 @@ export {
ArrowDownOutlined,
CalculatorOutlined,
DollarOutlined,
DollarCircleOutlined
DollarCircleOutlined,
DatabaseOutlined
} from '@ant-design/icons';
// Re-export all components with React

View File

@@ -0,0 +1,71 @@
export interface IHolidayType {
id: string;
name: string;
description?: string;
color_code: string;
created_at: string;
updated_at: string;
}
export interface IOrganizationHoliday {
id: string;
organization_id: string;
holiday_type_id: string;
name: string;
description?: string;
date: string;
is_recurring: boolean;
created_at: string;
updated_at: string;
holiday_type_name?: string;
color_code?: string;
}
export interface ICountryHoliday {
id: string;
country_code: string;
name: string;
description?: string;
date: string;
is_recurring: boolean;
created_at: string;
updated_at: string;
}
export interface IAvailableCountry {
code: string;
name: string;
}
export interface ICreateHolidayRequest {
name: string;
description?: string;
date: string;
holiday_type_id: string;
is_recurring?: boolean;
}
export interface IUpdateHolidayRequest {
id: string;
name?: string;
description?: string;
date?: string;
holiday_type_id?: string;
is_recurring?: boolean;
}
export interface IImportCountryHolidaysRequest {
country_code: string;
year?: number;
}
export interface IHolidayCalendarEvent {
id: string;
name: string;
description?: string;
date: string;
is_recurring: boolean;
holiday_type_name: string;
color_code: string;
source: 'organization' | 'country';
}