diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/CalendarView.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/CalendarView.tsx index 360aced88..e1b649a62 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/CalendarView.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/CalendarView.tsx @@ -12,6 +12,7 @@ import MonthlyTimesheetCalendar from "./MonthlyTimesheetCalendar"; import { useTimelogFilterOptions } from "@/app/hooks"; import WeeklyTimesheetCalendar from "./WeeklyTimesheetCalendar"; interface BaseCalendarDataViewProps { + t: TranslationHooks data: GroupedTimesheet[]; daysLabels?: string[]; CalendarComponent: typeof MonthlyTimesheetCalendar | typeof WeeklyTimesheetCalendar; @@ -35,9 +36,9 @@ export function CalendarView({ data, loading }: { data?: GroupedTimesheet[], loa data.length > 0 ? ( <> {timesheetGroupByDays === 'Monthly' ? ( - + ) : timesheetGroupByDays === 'Weekly' ? ( - + ) : ( )} @@ -99,7 +100,7 @@ const CalendarDataView = ({ data, t }: { data?: GroupedTimesheet[], t: Translati
- + {status === 'DENIED' ? 'REJECTED' : status} ({rows.length}) @@ -166,10 +167,11 @@ const CalendarDataView = ({ data, t }: { data?: GroupedTimesheet[], t: Translati ) } -const BaseCalendarDataView = ({ data, daysLabels, CalendarComponent }: BaseCalendarDataViewProps) => { +const BaseCalendarDataView = ({ data, daysLabels, t, CalendarComponent }: BaseCalendarDataViewProps) => { const { getStatusTimesheet } = useTimesheet({}); return (
- + {status === 'DENIED' ? 'REJECTED' : status} - ({rows.length}) + ({rows.length})
@@ -262,10 +264,10 @@ const BaseCalendarDataView = ({ data, daysLabels, CalendarComponent }: BaseCalen ); }; -const MonthlyCalendarDataView = (props: { data: GroupedTimesheet[], daysLabels?: string[] }) => ( +const MonthlyCalendarDataView = (props: { data: GroupedTimesheet[], t: TranslationHooks, daysLabels?: string[] }) => ( ); -const WeeklyCalendarDataView = (props: { data: GroupedTimesheet[], daysLabels?: string[] }) => ( +const WeeklyCalendarDataView = (props: { data: GroupedTimesheet[], t: TranslationHooks, daysLabels?: string[] }) => ( ); diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/FilterWithStatus.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/FilterWithStatus.tsx index 14ac28f0c..8e4c98a06 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/FilterWithStatus.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/FilterWithStatus.tsx @@ -3,6 +3,7 @@ import React, { HTMLAttributes } from 'react'; import { Button } from 'lib/components'; import { clsxm } from '@app/utils'; import { TimesheetLog, TimesheetStatus } from '@/app/interfaces'; +import { useTranslations } from 'next-intl'; export type FilterStatus = 'All Tasks' | 'Pending' | 'Approved' | 'In review' | 'Draft' | 'Rejected'; export function FilterWithStatus({ @@ -17,6 +18,7 @@ export function FilterWithStatus({ onToggle: (status: FilterStatus) => void; className?: HTMLAttributes; }>) { + const t = useTranslations(); const statusIcons: Record = { 'All Tasks': 'icon-all', @@ -29,12 +31,12 @@ export function FilterWithStatus({ const buttonData = React.useMemo(() => { const counts = { - 'All Tasks': Object.values(data ?? {}).reduce((total, tasks) => total + (tasks?.length ?? 0), 0), - Pending: data?.PENDING?.length ?? 0, - Approved: data?.APPROVED?.length ?? 0, - 'In review': data?.['IN REVIEW']?.length ?? 0, - Draft: data?.DRAFT?.length ?? 0, - Rejected: data?.DENIED?.length ?? 0, + [t('pages.timesheet.ALL_TASKS')]: Object.values(data ?? {}).reduce((total, tasks) => total + (tasks?.length ?? 0), 0), + [t('pages.timesheet.PENDING')]: data?.PENDING?.length ?? 0, + [t('pages.timesheet.APPROVED')]: data?.APPROVED?.length ?? 0, + [t('pages.timesheet.IN_REVIEW')]: data?.['IN REVIEW']?.length ?? 0, + [t('pages.timesheet.DRAFT')]: data?.DRAFT?.length ?? 0, + [t('pages.timesheet.REJECTED')]: data?.DENIED?.length ?? 0, }; return Object.entries(counts).map(([label, count]) => ({ label: label as FilterStatus, diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/MonthlyTimesheetCalendar.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/MonthlyTimesheetCalendar.tsx index 55f6daad5..c2da23148 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/MonthlyTimesheetCalendar.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/MonthlyTimesheetCalendar.tsx @@ -1,12 +1,14 @@ import React, { useMemo, useState, useCallback } from "react"; -import { format, addMonths, eachDayOfInterval, startOfMonth, endOfMonth, addDays, Locale } from "date-fns"; +import { format, addMonths, eachDayOfInterval, startOfMonth, endOfMonth, addDays, Locale, isLeapYear } from "date-fns"; import { GroupedTimesheet } from "@/app/hooks/features/useTimesheet"; import { enGB } from 'date-fns/locale'; import { cn } from "@/lib/utils"; import { TotalDurationByDate } from "@/lib/features"; import { formatDate } from "@/app/helpers"; +import { TranslationHooks } from "next-intl"; type MonthlyCalendarDataViewProps = { + t: TranslationHooks data?: GroupedTimesheet[]; onDateClick?: (date: Date) => void; renderDayContent?: (date: Date, plan?: GroupedTimesheet) => React.ReactNode; @@ -26,7 +28,14 @@ const defaultDaysLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const generateFullCalendar = (currentMonth: Date) => { const monthStart = startOfMonth(currentMonth); - const monthEnd = endOfMonth(currentMonth); + const monthEnd = (() => { + const month = monthStart.getMonth(); + if (month === 1) { + const year = monthStart.getFullYear(); + return new Date(year, 1, isLeapYear(monthStart) ? 29 : 28); + } + return endOfMonth(monthStart); + })(); const startDate = addDays(monthStart, -monthStart.getDay()); const endDate = addDays(monthEnd, 6 - monthEnd.getDay()); return eachDayOfInterval({ start: startDate, end: endDate }); @@ -40,7 +49,8 @@ const MonthlyTimesheetCalendar: React.FC = ({ locale = enGB, daysLabels = defaultDaysLabels, noDataText = "No Data", - classNames = {} + classNames = {}, + t }) => { const [currentMonth, setCurrentMonth] = useState(new Date()); const calendarDates = useMemo(() => generateFullCalendar(currentMonth), [currentMonth]); @@ -60,7 +70,7 @@ const MonthlyTimesheetCalendar: React.FC = ({ onClick={handlePreviousMonth} className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300 dark:bg-primary-light hover:dark:bg-primary-light" > - Previous + {t('common.PREV')}

{format(currentMonth, "MMMM yyyy", { locale: locale })} @@ -69,7 +79,7 @@ const MonthlyTimesheetCalendar: React.FC = ({ onClick={handleNextMonth} className="px-4 py-2 bg-gray-200 dark:bg-primary-light rounded hover:bg-gray-300 hover:dark:bg-primary-light" > - Next + {t('common.NEXT')}

diff --git a/apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx b/apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx index 8eb858acc..8cb3c1675 100644 --- a/apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx +++ b/apps/web/app/[locale]/timesheet/[memberId]/components/TimeSheetFilterPopover.tsx @@ -6,9 +6,9 @@ import { MultiSelect } from 'lib/components/custom-select'; import { Popover, PopoverContent, PopoverTrigger } from '@components/ui/popover'; import { SettingFilterIcon } from '@/assets/svg'; import { useTranslations } from 'next-intl'; -import { clsxm } from '@/app/utils'; import { useTimelogFilterOptions } from '@/app/hooks'; import { useTimesheet } from '@/app/hooks/features/useTimesheet'; +import { cn } from '@/lib/utils'; export const TimeSheetFilterPopover = React.memo(function TimeSheetFilterPopover() { const [shouldRemoveItems, setShouldRemoveItems] = React.useState(false); @@ -61,7 +61,7 @@ export const TimeSheetFilterPopover = React.memo(function TimeSheetFilterPopover
{ return
0 && status &&
-
+
{status === 'DENIED' ? 'REJECTED' : status} @@ -144,17 +144,17 @@ export const TimesheetCardDetail = ({ data }: { data?: Record
void; renderDayContent?: (date: Date, plan?: GroupedTimesheet) => React.ReactNode; @@ -38,6 +40,7 @@ const WeeklyTimesheetCalendar: React.FC = ({ daysLabels = defaultDaysLabels, noDataText = "No Data", classNames = {}, + t }) => { const [currentDate, setCurrentDate] = useState(new Date()); @@ -61,7 +64,7 @@ const WeeklyTimesheetCalendar: React.FC = ({ onClick={handlePreviousWeek} className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300 dark:bg-primary-light hover:dark:bg-primary-light" > - Previous + {t('common.PREV')}

{`Week of ${format(weekDates[0], "MMM d", { locale })} - ${format( @@ -74,7 +77,7 @@ const WeeklyTimesheetCalendar: React.FC = ({ onClick={handleNextWeek} className="px-4 py-2 bg-gray-200 dark:bg-primary-light rounded hover:bg-gray-300 hover:dark:bg-primary-light" > - Next + {t('common.NEXT')}

diff --git a/apps/web/locales/ar.json b/apps/web/locales/ar.json index c94dd66a5..eca9c7ba9 100644 --- a/apps/web/locales/ar.json +++ b/apps/web/locales/ar.json @@ -633,7 +633,13 @@ }, "LOADING": "جار تحميل بيانات الجدول الزمني...", "NO_ENTRIES_FOUND": "لم يتم العثور على مدخلات في الجدول الزمني", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "أنت على وشك رفض الإدخال المحدد، هل ترغب في المتابعة؟" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "أنت على وشك رفض الإدخال المحدد، هل ترغب في المتابعة؟", + "ALL_TASKS": "جميع المهام", + "PENDING": "قيد الانتظار", + "APPROVED": "تمت الموافقة", + "IN_REVIEW": "قيد المراجعة", + "DRAFT": "مسودة", + "REJECTED": "مرفوض" } }, "timer": { diff --git a/apps/web/locales/bg.json b/apps/web/locales/bg.json index 19cc6cc07..0cdd2c793 100644 --- a/apps/web/locales/bg.json +++ b/apps/web/locales/bg.json @@ -633,7 +633,13 @@ }, "LOADING": "Зареждане на данни за таблицата за работно време...", "NO_ENTRIES_FOUND": "Няма намерени записи в таблицата за работно време", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Вие ще отхвърлите избрания запис, искате ли да продължите?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Вие ще отхвърлите избрания запис, искате ли да продължите?", + "ALL_TASKS": "Всички задачи", + "PENDING": "Висящи", + "APPROVED": "Одобрено", + "IN_REVIEW": "В процес на проверка", + "DRAFT": "Черновик", + "REJECTED": "Отхвърлено" } }, "timer": { diff --git a/apps/web/locales/de.json b/apps/web/locales/de.json index fa9e07c55..72fea4a02 100644 --- a/apps/web/locales/de.json +++ b/apps/web/locales/de.json @@ -633,7 +633,13 @@ }, "LOADING": "Lade Zeiterfassungsdaten...", "NO_ENTRIES_FOUND": "Keine Zeiterfassungseinträge gefunden", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Sie sind dabei, den ausgewählten Eintrag abzulehnen. Möchten Sie fortfahren?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Sie sind dabei, den ausgewählten Eintrag abzulehnen. Möchten Sie fortfahren?", + "ALL_TASKS": "Alle Aufgaben", + "PENDING": "Ausstehend", + "APPROVED": "Genehmigt", + "IN_REVIEW": "In Überprüfung", + "DRAFT": "Entwurf", + "REJECTED": "Abgelehnt" } }, "timer": { diff --git a/apps/web/locales/en.json b/apps/web/locales/en.json index 978ff6969..a01c1cc56 100644 --- a/apps/web/locales/en.json +++ b/apps/web/locales/en.json @@ -633,7 +633,13 @@ }, "LOADING": "Loading timesheet data...", "NO_ENTRIES_FOUND": "No timesheet entries found", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "You are about to reject the selected entry, would you like to proceed?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "You are about to reject the selected entry, would you like to proceed?", + "ALL_TASKS": "All Tasks", + "PENDING": "Pending", + "APPROVED": "Approved", + "IN_REVIEW": "In review", + "DRAFT": "Draft", + "REJECTED": "Rejected" } }, "timer": { diff --git a/apps/web/locales/es.json b/apps/web/locales/es.json index 5a1b3a1c0..e9b0cadd0 100644 --- a/apps/web/locales/es.json +++ b/apps/web/locales/es.json @@ -633,7 +633,13 @@ }, "LOADING": "Cargando datos del registro de horas...", "NO_ENTRIES_FOUND": "No se encontraron entradas en el registro de horas", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Estás a punto de rechazar la entrada seleccionada, ¿te gustaría continuar?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Estás a punto de rechazar la entrada seleccionada, ¿te gustaría continuar?", + "ALL_TASKS": "Todas las tareas", + "PENDING": "Pendiente", + "APPROVED": "Aprobado", + "IN_REVIEW": "En revisión", + "DRAFT": "Borrador", + "REJECTED": "Rechazado" } }, "timer": { diff --git a/apps/web/locales/fr.json b/apps/web/locales/fr.json index 88e51f215..aa682d277 100644 --- a/apps/web/locales/fr.json +++ b/apps/web/locales/fr.json @@ -633,7 +633,13 @@ }, "LOADING": "Chargement des données de la feuille de temps...", "NO_ENTRIES_FOUND": "Aucune entrée de feuille de temps trouvée", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Vous êtes sur le point de rejeter l'entrée sélectionnée, souhaitez-vous continuer ?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Vous êtes sur le point de rejeter l'entrée sélectionnée, souhaitez-vous continuer ?", + "ALL_TASKS": "Toutes les tâches", + "PENDING": "En attente", + "APPROVED": "Approuvé", + "IN_REVIEW": "En cours d'examen", + "DRAFT": "Brouillon", + "REJECTED": "Rejeté" } }, "timer": { diff --git a/apps/web/locales/he.json b/apps/web/locales/he.json index 0177769f8..c1305fe5f 100644 --- a/apps/web/locales/he.json +++ b/apps/web/locales/he.json @@ -633,7 +633,13 @@ }, "LOADING": "טוען נתוני רשומת זמן...", "NO_ENTRIES_FOUND": "לא נמצאו רשומות זמן", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "אתה עומד לדחות את הרשומה הנבחרת, האם ברצונך להמשיך?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "אתה עומד לדחות את הרשומה הנבחרת, האם ברצונך להמשיך?", + "ALL_TASKS": "כל המשימות", + "PENDING": "ממתין", + "APPROVED": "מאושר", + "IN_REVIEW": "בבדיקה", + "DRAFT": "טיוטה", + "REJECTED": "נדחה" } }, "timer": { diff --git a/apps/web/locales/it.json b/apps/web/locales/it.json index b8e80cd98..02be4b0a7 100644 --- a/apps/web/locales/it.json +++ b/apps/web/locales/it.json @@ -633,7 +633,13 @@ }, "LOADING": "Caricamento dati della registrazione ore...", "NO_ENTRIES_FOUND": "Nessuna voce di registrazione ore trovata", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Stai per rifiutare l'elemento selezionato, vuoi procedere?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Stai per rifiutare l'elemento selezionato, vuoi procedere?", + "ALL_TASKS": "Tutti i compiti", + "PENDING": "In sospeso", + "APPROVED": "Approvato", + "IN_REVIEW": "In revisione", + "DRAFT": "Bozza", + "REJECTED": "Respinto" } }, "timer": { diff --git a/apps/web/locales/nl.json b/apps/web/locales/nl.json index 8768d926f..aa792b40a 100644 --- a/apps/web/locales/nl.json +++ b/apps/web/locales/nl.json @@ -633,7 +633,13 @@ }, "LOADING": "Tijdregistratiegegevens laden...", "NO_ENTRIES_FOUND": "Geen tijdregistratie-invoeren gevonden", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Je staat op het punt de geselecteerde invoer te weigeren, wil je doorgaan?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Je staat op het punt de geselecteerde invoer te weigeren, wil je doorgaan?", + "ALL_TASKS": "Alle taken", + "PENDING": "In behandeling", + "APPROVED": "Goedgekeurd", + "IN_REVIEW": "In beoordeling", + "DRAFT": "Concept", + "REJECTED": "Afgewezen" } }, "timer": { diff --git a/apps/web/locales/pl.json b/apps/web/locales/pl.json index 4b98a4d8b..0aac0db99 100644 --- a/apps/web/locales/pl.json +++ b/apps/web/locales/pl.json @@ -633,7 +633,13 @@ }, "LOADING": "Ładowanie danych rejestru czasu...", "NO_ENTRIES_FOUND": "Nie znaleziono żadnych wpisów w rejestrze czasu", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Zaraz odrzucisz wybraną pozycję, czy chcesz kontynuować?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Zaraz odrzucisz wybraną pozycję, czy chcesz kontynuować?", + "ALL_TASKS": "Wszystkie zadania", + "PENDING": "Oczekujące", + "APPROVED": "Zatwierdzone", + "IN_REVIEW": "W trakcie przeglądu", + "DRAFT": "Szkic", + "REJECTED": "Odrzucone" } }, "timer": { diff --git a/apps/web/locales/pt.json b/apps/web/locales/pt.json index a587193be..4db3c7e9d 100644 --- a/apps/web/locales/pt.json +++ b/apps/web/locales/pt.json @@ -634,7 +634,13 @@ }, "LOADING": "Загрузка данных учета рабочего времени...", "NO_ENTRIES_FOUND": "Записи учета рабочего времени не найдены", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Você está prestes a rejeitar a entrada selecionada, deseja continuar?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Você está prestes a rejeitar a entrada selecionada, deseja continuar?", + "ALL_TASKS": "Todas as tarefas", + "PENDING": "Pendente", + "APPROVED": "Aprovado", + "IN_REVIEW": "Em revisão", + "DRAFT": "Rascunho", + "REJECTED": "Rejeitado" } }, "timer": { diff --git a/apps/web/locales/ru.json b/apps/web/locales/ru.json index 0e7dab1af..65b25b194 100644 --- a/apps/web/locales/ru.json +++ b/apps/web/locales/ru.json @@ -633,7 +633,13 @@ }, "LOADING": "Загрузка данных учета времени...", "NO_ENTRIES_FOUND": "Записи учета времени не найдены", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Вы собираетесь отклонить выбранную запись, хотите продолжить?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "Вы собираетесь отклонить выбранную запись, хотите продолжить?", + "ALL_TASKS": "Все задачи", + "PENDING": "В ожидании", + "APPROVED": "Утверждено", + "IN_REVIEW": "На рассмотрении", + "DRAFT": "Черновик", + "REJECTED": "Отклонено" } }, "timer": { diff --git a/apps/web/locales/zh.json b/apps/web/locales/zh.json index f53aeb649..19482afe0 100644 --- a/apps/web/locales/zh.json +++ b/apps/web/locales/zh.json @@ -633,7 +633,13 @@ }, "LOADING": "加载时间表数据...", "NO_ENTRIES_FOUND": "未找到时间表记录", - "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "你即将拒绝选定的条目,是否继续?" + "YOU_ARE_ABOUT_TO_REJECT_ENTRY": "你即将拒绝选定的条目,是否继续?", + "ALL_TASKS": "所有任务", + "PENDING": "待处理", + "APPROVED": "已批准", + "IN_REVIEW": "审核中", + "DRAFT": "草稿", + "REJECTED": "拒绝" } }, "timer": {