feat(holiday-calendar): implement holiday calendar component with CRUD functionality
- Added a new `HolidayCalendar` component for managing custom and official holidays. - Integrated holiday type fetching and population logic to streamline holiday management. - Enhanced UI with modals for creating, editing, and deleting holidays, including validation and user feedback. - Updated styles for improved visual presentation and user interaction. - Refactored sidebar and routes to accommodate new component structure and localization updates.
This commit is contained in:
@@ -4,6 +4,6 @@
|
|||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
"billing": "Billing",
|
"billing": "Billing",
|
||||||
"projects": "Projects",
|
"projects": "Projects",
|
||||||
"settings": "Settings",
|
"settings": "Utilization Settings",
|
||||||
"adminCenter": "Admin Center"
|
"adminCenter": "Admin Center"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
ICombinedHolidaysRequest,
|
ICombinedHolidaysRequest,
|
||||||
IHolidayDateRange,
|
IHolidayDateRange,
|
||||||
} from '@/types/holiday/holiday.types';
|
} from '@/types/holiday/holiday.types';
|
||||||
|
import logger from '@/utils/errorLogger';
|
||||||
|
import { error } from 'console';
|
||||||
|
|
||||||
const rootUrl = `${API_BASE_URL}/holidays`;
|
const rootUrl = `${API_BASE_URL}/holidays`;
|
||||||
|
|
||||||
@@ -764,9 +766,7 @@ export const holidayApiService = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -777,6 +777,7 @@ export const holidayApiService = {
|
|||||||
description: h.description,
|
description: h.description,
|
||||||
date: h.date,
|
date: h.date,
|
||||||
is_recurring: h.is_recurring,
|
is_recurring: h.is_recurring,
|
||||||
|
holiday_type_id: h.holiday_type_id,
|
||||||
holiday_type_name: h.holiday_type_name || 'Custom',
|
holiday_type_name: h.holiday_type_name || 'Custom',
|
||||||
color_code: h.color_code || '#f37070',
|
color_code: h.color_code || '#f37070',
|
||||||
source: h.source || 'custom' as const,
|
source: h.source || 'custom' as const,
|
||||||
@@ -787,19 +788,14 @@ export const holidayApiService = {
|
|||||||
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,
|
||||||
} as IServerResponse<IHolidayCalendarEvent[]>;
|
} as IServerResponse<IHolidayCalendarEvent[]>;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching combined holidays:', error);
|
logger.error('Error fetching combined holidays:', error);
|
||||||
return {
|
return {
|
||||||
done: false,
|
done: false,
|
||||||
body: [],
|
body: [],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { RouteObject } from 'react-router-dom';
|
import { RouteObject } from 'react-router-dom';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { adminCenterItems } from '@/pages/admin-center/admin-center-constants';
|
import { adminCenterItems } from '@/lib/admin-center-constants';
|
||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
import { useAuthService } from '@/hooks/useAuth';
|
import { useAuthService } from '@/hooks/useAuth';
|
||||||
import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
|
import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ interface HolidayCalendarProps {
|
|||||||
const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDays = [] }) => {
|
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, holidaySettings } = useAppSelector(
|
||||||
(state: RootState) => state.adminCenterReducer
|
(state: RootState) => state.adminCenterReducer
|
||||||
);
|
);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -57,6 +57,7 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs());
|
const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs());
|
||||||
const [isPopulatingHolidays, setIsPopulatingHolidays] = useState(false);
|
const [isPopulatingHolidays, setIsPopulatingHolidays] = useState(false);
|
||||||
const [hasAttemptedPopulation, setHasAttemptedPopulation] = useState(false);
|
const [hasAttemptedPopulation, setHasAttemptedPopulation] = useState(false);
|
||||||
|
const [isNavigating, setIsNavigating] = useState(false);
|
||||||
|
|
||||||
const fetchHolidayTypes = async () => {
|
const fetchHolidayTypes = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -79,18 +80,16 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
!isPopulatingHolidays
|
!isPopulatingHolidays
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
console.log('🔄 No holidays found, attempting to populate official holidays...');
|
|
||||||
setIsPopulatingHolidays(true);
|
setIsPopulatingHolidays(true);
|
||||||
setHasAttemptedPopulation(true);
|
setHasAttemptedPopulation(true);
|
||||||
|
|
||||||
const populateRes = await holidayApiService.populateCountryHolidays();
|
const populateRes = await holidayApiService.populateCountryHolidays();
|
||||||
if (populateRes.done) {
|
if (populateRes.done) {
|
||||||
console.log('✅ Official holidays populated successfully');
|
|
||||||
// Refresh holidays after population
|
// Refresh holidays after population
|
||||||
fetchHolidaysForDateRange(true);
|
fetchHolidaysForDateRange(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('⚠️ Could not populate official holidays:', error);
|
logger.error('populateHolidaysIfNeeded', error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsPopulatingHolidays(false);
|
setIsPopulatingHolidays(false);
|
||||||
}
|
}
|
||||||
@@ -217,7 +216,7 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
name: holiday.name,
|
name: holiday.name,
|
||||||
description: holiday.description,
|
description: holiday.description,
|
||||||
date: dayjs(holiday.date),
|
date: dayjs(holiday.date),
|
||||||
holiday_type_id: holiday.holiday_type_name, // This might need adjustment based on backend
|
holiday_type_id: holiday.holiday_type_id,
|
||||||
is_recurring: holiday.is_recurring,
|
is_recurring: holiday.is_recurring,
|
||||||
});
|
});
|
||||||
setEditModalVisible(true);
|
setEditModalVisible(true);
|
||||||
@@ -227,12 +226,15 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
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');
|
const dayName = date.format('dddd');
|
||||||
// Check if this day is in the working days array from API response
|
// Check if this day is in the working days array from API response
|
||||||
const isWorkingDay = workingDays && workingDays.length > 0 ? workingDays.includes(dayName) : false;
|
const isWorkingDay =
|
||||||
|
workingDays && workingDays.length > 0 ? workingDays.includes(dayName) : false;
|
||||||
const isToday = date.isSame(dayjs(), 'day');
|
const isToday = date.isSame(dayjs(), 'day');
|
||||||
const isCurrentMonth = date.isSame(currentDate, 'month');
|
const isCurrentMonth = date.isSame(currentDate, 'month');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`calendar-cell ${isWorkingDay ? 'working-day' : 'non-working-day'} ${isToday ? 'today' : ''} ${!isCurrentMonth ? 'other-month' : ''}`}>
|
<div
|
||||||
|
className={`calendar-cell ${isWorkingDay ? 'working-day' : 'non-working-day'} ${isToday ? 'today' : ''} ${!isCurrentMonth ? 'other-month' : ''}`}
|
||||||
|
>
|
||||||
{dateHolidays.length > 0 && (
|
{dateHolidays.length > 0 && (
|
||||||
<div className="holiday-cell">
|
<div className="holiday-cell">
|
||||||
{dateHolidays.map((holiday, index) => {
|
{dateHolidays.map((holiday, index) => {
|
||||||
@@ -250,16 +252,22 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
fontWeight: isCustom ? 600 : 500,
|
fontWeight: isCustom ? 600 : 500,
|
||||||
border: isCustom ? '1px solid rgba(82, 196, 26, 0.6)' : '1px solid rgba(24, 144, 255, 0.4)',
|
border: isCustom
|
||||||
|
? '1px solid rgba(82, 196, 26, 0.6)'
|
||||||
|
: '1px solid rgba(24, 144, 255, 0.4)',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
title={`${holiday.name}${isOfficial ? ' (Official Holiday)' : ' (Custom Holiday)'}`}
|
title={`${holiday.name}${isOfficial ? ' (Official Holiday)' : ' (Custom Holiday)'}`}
|
||||||
>
|
>
|
||||||
{isCustom && (
|
{isCustom && (
|
||||||
<span className="custom-holiday-icon" style={{ marginRight: '2px' }}>⭐</span>
|
<span className="custom-holiday-icon" style={{ marginRight: '2px' }}>
|
||||||
|
⭐
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{isOfficial && (
|
{isOfficial && (
|
||||||
<span className="official-holiday-icon" style={{ marginRight: '2px' }}>🏛️</span>
|
<span className="official-holiday-icon" style={{ marginRight: '2px' }}>
|
||||||
|
🏛️
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{holiday.name}
|
{holiday.name}
|
||||||
</Tag>
|
</Tag>
|
||||||
@@ -277,10 +285,23 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onPanelChange = (value: Dayjs) => {
|
const onPanelChange = (value: Dayjs) => {
|
||||||
|
setIsNavigating(true);
|
||||||
setCurrentDate(value);
|
setCurrentDate(value);
|
||||||
|
// Reset navigation flag after a short delay to allow the onSelect event to check it
|
||||||
|
setTimeout(() => setIsNavigating(false), 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDateSelect = (date: Dayjs) => {
|
const onDateSelect = (date: Dayjs) => {
|
||||||
|
// Prevent modal from opening during navigation (month/year changes)
|
||||||
|
if (isNavigating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent modal from opening if the date is from a different month (navigation click)
|
||||||
|
if (!date.isSame(currentDate, 'month')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there's already a custom holiday on this date
|
// Check if there's already a custom holiday on this date
|
||||||
const existingCustomHoliday = holidays.find(
|
const existingCustomHoliday = holidays.find(
|
||||||
h => dayjs(h.date).isSame(date, 'day') && h.source === 'custom' && h.is_editable
|
h => dayjs(h.date).isSame(date, 'day') && h.source === 'custom' && h.is_editable
|
||||||
@@ -336,11 +357,11 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
boxShadow: '0 2px 8px rgba(24, 144, 255, 0.2)',
|
boxShadow: '0 2px 8px rgba(24, 144, 255, 0.2)',
|
||||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={e => {
|
||||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||||
e.currentTarget.style.boxShadow = '0 4px 12px rgba(24, 144, 255, 0.3)';
|
e.currentTarget.style.boxShadow = '0 4px 12px rgba(24, 144, 255, 0.3)';
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={e => {
|
||||||
e.currentTarget.style.transform = 'translateY(0)';
|
e.currentTarget.style.transform = 'translateY(0)';
|
||||||
e.currentTarget.style.boxShadow = '0 2px 8px rgba(24, 144, 255, 0.2)';
|
e.currentTarget.style.boxShadow = '0 2px 8px rgba(24, 144, 255, 0.2)';
|
||||||
}}
|
}}
|
||||||
@@ -349,7 +370,7 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="calendar-container">
|
<div className={`calendar-container holiday-calendar ${themeMode}`}>
|
||||||
<Calendar
|
<Calendar
|
||||||
value={currentDate}
|
value={currentDate}
|
||||||
onPanelChange={onPanelChange}
|
onPanelChange={onPanelChange}
|
||||||
@@ -474,7 +495,7 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
setSelectedHoliday(null);
|
setSelectedHoliday(null);
|
||||||
}}
|
}}
|
||||||
footer={null}
|
footer={null}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<Form form={editForm} layout="vertical" onFinish={handleUpdateHoliday}>
|
<Form form={editForm} layout="vertical" onFinish={handleUpdateHoliday}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -540,9 +561,13 @@ const HolidayCalendar: React.FC<HolidayCalendarProps> = ({ themeMode, workingDay
|
|||||||
>
|
>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
{selectedHoliday && selectedHoliday.source === 'custom' && selectedHoliday.is_editable && (
|
{selectedHoliday &&
|
||||||
|
selectedHoliday.source === 'custom' &&
|
||||||
|
selectedHoliday.is_editable && (
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={t('deleteHolidayConfirm') || 'Are you sure you want to delete this holiday?'}
|
title={
|
||||||
|
t('deleteHolidayConfirm') || 'Are you sure you want to delete this holiday?'
|
||||||
|
}
|
||||||
onConfirm={() => handleDeleteHoliday(selectedHoliday.id)}
|
onConfirm={() => handleDeleteHoliday(selectedHoliday.id)}
|
||||||
okText={t('yes') || 'Yes'}
|
okText={t('yes') || 'Yes'}
|
||||||
cancelText={t('no') || 'No'}
|
cancelText={t('no') || 'No'}
|
||||||
@@ -493,11 +493,13 @@
|
|||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid #d9d9d9;
|
border: 1px solid #d9d9d9;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar.dark .calendar-legend {
|
.holiday-calendar.dark .calendar-legend {
|
||||||
background: #1f1f1f;
|
background: #1f1f1f;
|
||||||
border-color: #434343;
|
border-color: #434343;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-item {
|
.legend-item {
|
||||||
@@ -505,11 +507,33 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: rgba(0, 0, 0, 0.65);
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar.dark .legend-item {
|
.holiday-calendar.dark .legend-item {
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legend item hover effects */
|
||||||
|
.legend-item:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item:hover .legend-badge {
|
||||||
|
transform: scale(1.1);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item:hover .legend-tag {
|
||||||
|
transform: scale(1.05);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item:hover .legend-dot {
|
||||||
|
transform: scale(1.2);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-dot {
|
.legend-dot {
|
||||||
@@ -531,6 +555,7 @@
|
|||||||
.legend-dot.today-dot {
|
.legend-dot.today-dot {
|
||||||
background: #1890ff;
|
background: #1890ff;
|
||||||
box-shadow: 0 0 0 1px rgba(24, 144, 255, 0.2);
|
box-shadow: 0 0 0 1px rgba(24, 144, 255, 0.2);
|
||||||
|
animation: todayPulse 2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar.dark .legend-dot.working-day-dot {
|
.holiday-calendar.dark .legend-dot.working-day-dot {
|
||||||
@@ -546,6 +571,7 @@
|
|||||||
.holiday-calendar.dark .legend-dot.today-dot {
|
.holiday-calendar.dark .legend-dot.today-dot {
|
||||||
background: #1890ff;
|
background: #1890ff;
|
||||||
box-shadow: 0 0 0 1px rgba(24, 144, 255, 0.3);
|
box-shadow: 0 0 0 1px rgba(24, 144, 255, 0.3);
|
||||||
|
animation: todayPulse 2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Legend badge and tag styles */
|
/* Legend badge and tag styles */
|
||||||
@@ -559,19 +585,22 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-badge.working-day-badge {
|
.legend-badge.working-day-badge {
|
||||||
background: #52c41a;
|
background: #52c41a;
|
||||||
border: 1px solid #d9f7be;
|
border: 1px solid #d9f7be;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
box-shadow: 0 1px 3px rgba(82, 196, 26, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar.dark .legend-badge.working-day-badge {
|
.holiday-calendar.dark .legend-badge.working-day-badge {
|
||||||
background: #52c41a;
|
background: #52c41a;
|
||||||
border: 1px solid #237804;
|
border: 1px solid #237804;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
box-shadow: 0 1px 3px rgba(82, 196, 26, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-tag {
|
.legend-tag {
|
||||||
@@ -583,35 +612,41 @@
|
|||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-tag-text {
|
.legend-tag-text {
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-holiday-legend {
|
.custom-holiday-legend {
|
||||||
background: #f6ffed;
|
background: #f6ffed;
|
||||||
border: 1px solid #b7eb8f;
|
border: 1px solid #b7eb8f;
|
||||||
color: #52c41a;
|
color: #52c41a;
|
||||||
|
box-shadow: 0 1px 2px rgba(82, 196, 26, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.official-holiday-legend {
|
.official-holiday-legend {
|
||||||
background: #e6f7ff;
|
background: #e6f7ff;
|
||||||
border: 1px solid #91d5ff;
|
border: 1px solid #91d5ff;
|
||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
|
box-shadow: 0 1px 2px rgba(24, 144, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar.dark .custom-holiday-legend {
|
.holiday-calendar.dark .custom-holiday-legend {
|
||||||
background: #162312;
|
background: #162312;
|
||||||
border: 1px solid #274916;
|
border: 1px solid #274916;
|
||||||
color: #95de64;
|
color: #95de64;
|
||||||
|
box-shadow: 0 1px 2px rgba(149, 222, 100, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.holiday-calendar.dark .official-holiday-legend {
|
.holiday-calendar.dark .official-holiday-legend {
|
||||||
background: #111b26;
|
background: #111b26;
|
||||||
border: 1px solid #13334c;
|
border: 1px solid #13334c;
|
||||||
color: #69c0ff;
|
color: #69c0ff;
|
||||||
|
box-shadow: 0 1px 2px rgba(105, 192, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-tag .custom-holiday-icon,
|
.legend-tag .custom-holiday-icon,
|
||||||
@@ -646,6 +681,16 @@
|
|||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.legend-item:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item:hover .legend-badge,
|
||||||
|
.legend-item:hover .legend-tag,
|
||||||
|
.legend-item:hover .legend-dot {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.working-day-badge {
|
.working-day-badge {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
|
|||||||
@@ -7,18 +7,14 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||||
|
|
||||||
const AdminCenterLayout: React.FC = () => {
|
const AdminCenterLayout: React.FC = () => {
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const isTablet = useMediaQuery({ query: '(min-width:768px)' });
|
const isTablet = useMediaQuery({ query: '(min-width:768px)' });
|
||||||
const isMarginAvailable = useMediaQuery({ query: '(min-width: 1000px)' });
|
|
||||||
const { t } = useTranslation('admin-center/sidebar');
|
const { t } = useTranslation('admin-center/sidebar');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
marginBlock: 96,
|
marginBlock: 24,
|
||||||
minHeight: '90vh',
|
minHeight: '90vh',
|
||||||
marginLeft: `${isMarginAvailable ? '5%' : ''}`,
|
|
||||||
marginRight: `${isMarginAvailable ? '5%' : ''}`,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography.Title level={4}>{t('adminCenter')}</Typography.Title>
|
<Typography.Title level={4}>{t('adminCenter')}</Typography.Title>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const SettingsLayout = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBlock: 96, minHeight: '90vh' }}>
|
<div style={{ marginBlock: 24, minHeight: '90vh' }}>
|
||||||
<Typography.Title level={4}>Settings</Typography.Title>
|
<Typography.Title level={4}>Settings</Typography.Title>
|
||||||
|
|
||||||
{isTablet ? (
|
{isTablet ? (
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import {
|
|||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
} from '@/shared/antd-imports';
|
} from '@/shared/antd-imports';
|
||||||
import React, { ReactNode, lazy } from 'react';
|
import React, { ReactNode, lazy } from 'react';
|
||||||
const Overview = lazy(() => import('./overview/overview'));
|
const Overview = lazy(() => import('../pages/admin-center/overview/overview'));
|
||||||
const Users = lazy(() => import('./users/users'));
|
const Users = lazy(() => import('../pages/admin-center/users/users'));
|
||||||
const Teams = lazy(() => import('./teams/teams'));
|
const Teams = lazy(() => import('../pages/admin-center/teams/teams'));
|
||||||
const Billing = lazy(() => import('./billing/billing'));
|
const Billing = lazy(() => import('../pages/admin-center/billing/billing'));
|
||||||
const Projects = lazy(() => import('./projects/projects'));
|
const Projects = lazy(() => import('../pages/admin-center/projects/projects'));
|
||||||
const Settings = lazy(() => import('./settings/settings'));
|
const Settings = lazy(() => import('../pages/admin-center/settings/Settings'));
|
||||||
|
|
||||||
// type of a menu item in admin center sidebar
|
// type of a menu item in admin center sidebar
|
||||||
type AdminCenterMenuItems = {
|
type AdminCenterMenuItems = {
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default } from './settings';
|
|
||||||
@@ -20,7 +20,7 @@ import { scheduleAPIService } from '@/api/schedule/schedule.api.service';
|
|||||||
import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service';
|
import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service';
|
||||||
import { Settings } from '@/types/schedule/schedule-v2.types';
|
import { Settings } from '@/types/schedule/schedule-v2.types';
|
||||||
import OrganizationCalculationMethod from '@/components/admin-center/overview/organization-calculation-method/organization-calculation-method';
|
import OrganizationCalculationMethod from '@/components/admin-center/overview/organization-calculation-method/organization-calculation-method';
|
||||||
import HolidayCalendar from '@/components/admin-center/overview/holiday-calendar/holiday-calendar';
|
import HolidayCalendar from '@/components/admin-center/overview/holiday-calendar/HolidayCalendar';
|
||||||
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';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React from 'react';
|
|||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { colors } from '../../../styles/colors';
|
import { colors } from '../../../styles/colors';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { adminCenterItems } from '../admin-center-constants';
|
import { adminCenterItems } from '../../../lib/admin-center-constants';
|
||||||
import './sidebar.css';
|
import './sidebar.css';
|
||||||
|
|
||||||
const AdminCenterSidebar: React.FC = () => {
|
const AdminCenterSidebar: React.FC = () => {
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ export interface IHolidayCalendarEvent {
|
|||||||
description?: string;
|
description?: string;
|
||||||
date: string;
|
date: string;
|
||||||
is_recurring: boolean;
|
is_recurring: boolean;
|
||||||
|
holiday_type_id?: string;
|
||||||
holiday_type_name: string;
|
holiday_type_name: string;
|
||||||
color_code: string;
|
color_code: string;
|
||||||
source: 'official' | 'custom';
|
source: 'official' | 'custom';
|
||||||
|
|||||||
Reference in New Issue
Block a user