diff --git a/src/components/views/TimesheetsView.tsx b/src/components/views/TimesheetsView.tsx index 1bf6634..7e9b15f 100644 --- a/src/components/views/TimesheetsView.tsx +++ b/src/components/views/TimesheetsView.tsx @@ -32,6 +32,7 @@ import { TimesheetTabs } from '@/components/timesheets/TimesheetTabs' import { useTimeTracking } from '@/hooks/use-time-tracking' import { useTimesheetsCrud } from '@/hooks/use-timesheets-crud' import { usePermissions } from '@/hooks/use-permissions' +import { useTranslation } from '@/hooks/use-translation' import { toast } from 'sonner' import type { Timesheet, TimesheetStatus, ShiftEntry } from '@/lib/types' @@ -46,6 +47,7 @@ export function TimesheetsView({ setSearchQuery, onCreateInvoice }: TimesheetsViewProps) { + const { t } = useTranslation() const [statusFilter, setStatusFilter] = useState<'all' | TimesheetStatus>('all') const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false) const [isBulkImportOpen, setIsBulkImportOpen] = useState(false) @@ -90,13 +92,13 @@ export function TimesheetsView({ submittedDate: new Date().toISOString(), shifts: [] }) - toast.success('Timesheet created successfully') + toast.success(t('timesheets.createSuccess')) setIsCreateDialogOpen(false) } catch (error) { - toast.error('Failed to create timesheet') + toast.error(t('timesheets.createError')) console.error('Error creating timesheet:', error) } - }, [createTimesheet]) + }, [createTimesheet, t]) const handleCreateDetailedTimesheet = useCallback(async (data: { workerName: string @@ -120,13 +122,13 @@ export function TimesheetsView({ submittedDate: new Date().toISOString(), shifts: data.shifts }) - toast.success('Detailed timesheet created successfully') + toast.success(t('timesheets.createDetailedSuccess')) setIsCreateDialogOpen(false) } catch (error) { - toast.error('Failed to create detailed timesheet') + toast.error(t('timesheets.createDetailedError')) console.error('Error creating detailed timesheet:', error) } - }, [createTimesheet]) + }, [createTimesheet, t]) const handleBulkImport = useCallback(async (csvData: string) => { try { @@ -156,17 +158,17 @@ export function TimesheetsView({ }) await bulkCreateTimesheets(timesheetsData) - toast.success(`${timesheetsData.length} timesheets imported successfully`) + toast.success(t('timesheets.importSuccess', { count: timesheetsData.length })) setIsBulkImportOpen(false) } catch (error) { - toast.error('Failed to import timesheets') + toast.error(t('timesheets.importError')) console.error('Error importing timesheets:', error) } - }, [bulkCreateTimesheets]) + }, [bulkCreateTimesheets, t]) const handleApprove = useCallback(async (id: string) => { if (!hasPermission('timesheets.approve')) { - toast.error('You do not have permission to approve timesheets') + toast.error(t('timesheets.noPermissionApprove')) return } @@ -175,16 +177,16 @@ export function TimesheetsView({ status: 'approved', approvedDate: new Date().toISOString() }) - toast.success('Timesheet approved') + toast.success(t('timesheets.approveSuccess')) } catch (error) { - toast.error('Failed to approve timesheet') + toast.error(t('timesheets.approveError')) console.error('Error approving timesheet:', error) } - }, [updateTimesheet, hasPermission]) + }, [updateTimesheet, hasPermission, t]) const handleReject = useCallback(async (id: string) => { if (!hasPermission('timesheets.approve')) { - toast.error('You do not have permission to reject timesheets') + toast.error(t('timesheets.noPermissionReject')) return } @@ -192,28 +194,28 @@ export function TimesheetsView({ await updateTimesheet(id, { status: 'rejected' }) - toast.error('Timesheet rejected') + toast.error(t('timesheets.rejectSuccess')) } catch (error) { - toast.error('Failed to reject timesheet') + toast.error(t('timesheets.rejectError')) console.error('Error rejecting timesheet:', error) } - }, [updateTimesheet, hasPermission]) + }, [updateTimesheet, hasPermission, t]) const handleAdjust = useCallback(async (timesheetId: string, adjustment: any) => { if (!hasPermission('timesheets.edit')) { - toast.error('You do not have permission to adjust timesheets') + toast.error(t('timesheets.noPermissionEdit')) return } try { await updateTimesheet(timesheetId, adjustment) - toast.success('Timesheet adjusted') + toast.success(t('timesheets.adjustSuccess')) setSelectedTimesheet(null) } catch (error) { - toast.error('Failed to adjust timesheet') + toast.error(t('timesheets.adjustError')) console.error('Error adjusting timesheet:', error) } - }, [updateTimesheet, hasPermission]) + }, [updateTimesheet, hasPermission, t]) const handleTimeAndRateAdjustment = useCallback(async (adjustment: { timesheetId: string @@ -230,7 +232,7 @@ export function TimesheetsView({ notes?: string }) => { if (!hasPermission('timesheets.edit')) { - toast.error('You do not have permission to adjust timesheets') + toast.error(t('timesheets.noPermissionEdit')) return } @@ -276,30 +278,30 @@ export function TimesheetsView({ await updateTimesheet(adjustment.timesheetId, updates) if (adjustment.approvalRequired) { - toast.success('Adjustment submitted for approval') + toast.success(t('timesheets.adjustmentSubmitted')) } else { - toast.success('Adjustment applied successfully') + toast.success(t('timesheets.adjustmentApplied')) } } catch (error) { - toast.error('Failed to apply adjustment') + toast.error(t('timesheets.adjustmentError')) console.error('Error applying adjustment:', error) } - }, [updateTimesheet, hasPermission, timesheets]) + }, [updateTimesheet, hasPermission, timesheets, t]) const handleDelete = useCallback(async (id: string) => { if (!hasPermission('timesheets.delete')) { - toast.error('You do not have permission to delete timesheets') + toast.error(t('timesheets.noPermissionDelete')) return } try { await deleteTimesheet(id) - toast.success('Timesheet deleted') + toast.success(t('timesheets.deleteSuccess')) } catch (error) { - toast.error('Failed to delete timesheet') + toast.error(t('timesheets.deleteError')) console.error('Error deleting timesheet:', error) } - }, [deleteTimesheet, hasPermission]) + }, [deleteTimesheet, hasPermission, t]) const timesheetsToFilter = useMemo(() => { return timesheets.filter(t => { @@ -348,16 +350,16 @@ export function TimesheetsView({ const [csvData, setCsvData] = useState('') const timesheetFields: FilterField[] = [ - { name: 'workerName', label: 'Worker Name', type: 'text' }, - { name: 'clientName', label: 'Client Name', type: 'text' }, - { name: 'status', label: 'Status', type: 'select', options: [ - { value: 'pending', label: 'Pending' }, - { value: 'approved', label: 'Approved' }, - { value: 'rejected', label: 'Rejected' } + { name: 'workerName', label: t('timesheets.workerName'), type: 'text' }, + { name: 'clientName', label: t('timesheets.clientName'), type: 'text' }, + { name: 'status', label: t('timesheets.status.all'), type: 'select', options: [ + { value: 'pending', label: t('timesheets.status.pending') }, + { value: 'approved', label: t('timesheets.status.approved') }, + { value: 'rejected', label: t('timesheets.status.rejected') } ]}, - { name: 'hours', label: 'Hours', type: 'number' }, - { name: 'amount', label: 'Amount', type: 'number' }, - { name: 'weekEnding', label: 'Week Ending', type: 'date' } + { name: 'hours', label: t('timesheets.hours'), type: 'number' }, + { name: 'amount', label: t('timesheets.amount'), type: 'number' }, + { name: 'weekEnding', label: t('timesheets.weekEnding'), type: 'date' } ] const pendingCount = filteredTimesheets.filter(ts => ts.status === 'pending').length @@ -371,8 +373,8 @@ export function TimesheetsView({ return ( } - description={`${pendingCount} pending review`} + description={t('timesheets.pendingReview', { count: pendingCount })} /> } - description="This period" + description={t('timesheets.thisPeriod')} /> } - description={validationStats.invalid > 0 ? 'Errors found' : 'All valid'} + description={validationStats.invalid > 0 ? t('timesheets.errorsFound') : t('timesheets.allValid')} /> } - description="Pending invoicing" + description={t('timesheets.pendingInvoicing')} /> @@ -432,12 +434,12 @@ export function TimesheetsView({
- Pending + {t('timesheets.pending')} {pendingCount}
- Awaiting approval + {t('timesheets.awaitingApproval')}
@@ -449,13 +451,13 @@ export function TimesheetsView({
- Approved + {t('timesheets.approved')} {approvedCount}
- Ready for billing + {t('timesheets.readyForBilling')}
@@ -467,13 +469,13 @@ export function TimesheetsView({
- Approval Rate + {t('timesheets.approvalRate')} {approvalRate.toFixed(0)}%
- This period + {t('timesheets.thisPeriod')}
@@ -492,13 +494,13 @@ export function TimesheetsView({
- Search & Filter + {t('timesheets.searchAndFilter')} - Find timesheets using advanced search + {t('timesheets.findTimesheetsAdvanced')}
- {filteredTimesheets.length} results + {t('timesheets.results', { count: filteredTimesheets.length })}
@@ -507,7 +509,7 @@ export function TimesheetsView({ items={timesheetsToFilter} fields={timesheetFields} onResultsChange={handleResultsChange} - placeholder="Search by worker, client, or status..." + placeholder={t('timesheets.searchPlaceholder')} /> @@ -522,24 +524,27 @@ export function TimesheetsView({
- All Status - Pending - Approved - Rejected + {t('timesheets.status.all')} + {t('timesheets.status.pending')} + {t('timesheets.status.approved')} + {t('timesheets.status.rejected')} {validationStats.invalid > 0 && ( - {validationStats.invalid} validation {validationStats.invalid === 1 ? 'error' : 'errors'} + {t('timesheets.validationErrors', { + count: validationStats.invalid, + errors: validationStats.invalid === 1 ? t('timesheets.error') : t('timesheets.errors') + })} )} diff --git a/src/data/translations/en.json b/src/data/translations/en.json index 7b9c260..d2c7cbe 100644 --- a/src/data/translations/en.json +++ b/src/data/translations/en.json @@ -115,7 +115,7 @@ }, "timesheets": { "title": "Timesheets", - "subtitle": "Manage and approve timesheet entries", + "subtitle": "Manage and approve worker timesheets", "addTimesheet": "Add Timesheet", "editTimesheet": "Edit Timesheet", "approveTimesheet": "Approve Timesheet", @@ -133,7 +133,9 @@ "submitted": "Submitted", "approved": "Approved", "rejected": "Rejected", - "processed": "Processed" + "processed": "Processed", + "pending": "Pending", + "all": "All Status" }, "hoursWorked": "Hours Worked", "overtimeHours": "Overtime Hours", @@ -142,7 +144,57 @@ "nightShift": "Night Shift", "weekendShift": "Weekend Shift", "adjustments": "Adjustments", - "comments": "Comments" + "comments": "Comments", + "analytics": "Analytics", + "showAnalytics": "Show Analytics", + "hideAnalytics": "Hide Analytics", + "totalTimesheets": "Total Timesheets", + "pendingReview": "{{count}} pending review", + "thisPeriod": "This period", + "validationIssues": "Validation Issues", + "errorsFound": "Errors found", + "allValid": "All valid", + "totalValue": "Total Value", + "pendingInvoicing": "Pending invoicing", + "pending": "Pending", + "awaitingApproval": "Awaiting approval", + "approved": "Approved", + "readyForBilling": "Ready for billing", + "approvalRate": "Approval Rate", + "searchAndFilter": "Search & Filter", + "findTimesheetsAdvanced": "Find timesheets using advanced search", + "results": "{{count}} results", + "searchPlaceholder": "Search by worker, client, or status...", + "allStatus": "All Status", + "validationErrors": "{{count}} validation {{errors}}", + "error": "error", + "errors": "errors", + "exportCsv": "Export CSV", + "workerName": "Worker Name", + "clientName": "Client Name", + "bulkImport": "Bulk Import", + "detailedEntry": "Detailed Entry", + "createSuccess": "Timesheet created successfully", + "createError": "Failed to create timesheet", + "createDetailedSuccess": "Detailed timesheet created successfully", + "createDetailedError": "Failed to create detailed timesheet", + "importSuccess": "{{count}} timesheets imported successfully", + "importError": "Failed to import timesheets", + "approveSuccess": "Timesheet approved", + "approveError": "Failed to approve timesheet", + "rejectSuccess": "Timesheet rejected", + "rejectError": "Failed to reject timesheet", + "adjustSuccess": "Timesheet adjusted", + "adjustError": "Failed to adjust timesheet", + "deleteSuccess": "Timesheet deleted", + "deleteError": "Failed to delete timesheet", + "noPermissionApprove": "You do not have permission to approve timesheets", + "noPermissionReject": "You do not have permission to reject timesheets", + "noPermissionEdit": "You do not have permission to adjust timesheets", + "noPermissionDelete": "You do not have permission to delete timesheets", + "adjustmentSubmitted": "Adjustment submitted for approval", + "adjustmentApplied": "Adjustment applied successfully", + "adjustmentError": "Failed to apply adjustment" }, "billing": { "title": "Billing", diff --git a/src/data/translations/es.json b/src/data/translations/es.json index 949e12c..987d0ac 100644 --- a/src/data/translations/es.json +++ b/src/data/translations/es.json @@ -115,7 +115,7 @@ }, "timesheets": { "title": "Hojas de Tiempo", - "subtitle": "Gestionar y aprobar entradas de hojas de tiempo", + "subtitle": "Gestionar y aprobar hojas de tiempo de trabajadores", "addTimesheet": "Agregar Hoja de Tiempo", "editTimesheet": "Editar Hoja de Tiempo", "approveTimesheet": "Aprobar Hoja de Tiempo", @@ -133,7 +133,9 @@ "submitted": "Enviado", "approved": "Aprobado", "rejected": "Rechazado", - "processed": "Procesado" + "processed": "Procesado", + "pending": "Pendiente", + "all": "Todos los Estados" }, "hoursWorked": "Horas Trabajadas", "overtimeHours": "Horas Extras", @@ -142,7 +144,57 @@ "nightShift": "Turno Nocturno", "weekendShift": "Turno de Fin de Semana", "adjustments": "Ajustes", - "comments": "Comentarios" + "comments": "Comentarios", + "analytics": "Análisis", + "showAnalytics": "Mostrar Análisis", + "hideAnalytics": "Ocultar Análisis", + "totalTimesheets": "Hojas de Tiempo Totales", + "pendingReview": "{{count}} pendientes de revisión", + "thisPeriod": "Este período", + "validationIssues": "Problemas de Validación", + "errorsFound": "Errores encontrados", + "allValid": "Todo válido", + "totalValue": "Valor Total", + "pendingInvoicing": "Facturación pendiente", + "pending": "Pendiente", + "awaitingApproval": "Esperando aprobación", + "approved": "Aprobado", + "readyForBilling": "Listo para facturar", + "approvalRate": "Tasa de Aprobación", + "searchAndFilter": "Buscar y Filtrar", + "findTimesheetsAdvanced": "Encontrar hojas de tiempo usando búsqueda avanzada", + "results": "{{count}} resultados", + "searchPlaceholder": "Buscar por trabajador, cliente o estado...", + "allStatus": "Todos los Estados", + "validationErrors": "{{count}} {{errors}} de validación", + "error": "error", + "errors": "errores", + "exportCsv": "Exportar CSV", + "workerName": "Nombre del Trabajador", + "clientName": "Nombre del Cliente", + "bulkImport": "Importación Masiva", + "detailedEntry": "Entrada Detallada", + "createSuccess": "Hoja de tiempo creada exitosamente", + "createError": "Fallo al crear hoja de tiempo", + "createDetailedSuccess": "Hoja de tiempo detallada creada exitosamente", + "createDetailedError": "Fallo al crear hoja de tiempo detallada", + "importSuccess": "{{count}} hojas de tiempo importadas exitosamente", + "importError": "Fallo al importar hojas de tiempo", + "approveSuccess": "Hoja de tiempo aprobada", + "approveError": "Fallo al aprobar hoja de tiempo", + "rejectSuccess": "Hoja de tiempo rechazada", + "rejectError": "Fallo al rechazar hoja de tiempo", + "adjustSuccess": "Hoja de tiempo ajustada", + "adjustError": "Fallo al ajustar hoja de tiempo", + "deleteSuccess": "Hoja de tiempo eliminada", + "deleteError": "Fallo al eliminar hoja de tiempo", + "noPermissionApprove": "No tienes permiso para aprobar hojas de tiempo", + "noPermissionReject": "No tienes permiso para rechazar hojas de tiempo", + "noPermissionEdit": "No tienes permiso para ajustar hojas de tiempo", + "noPermissionDelete": "No tienes permiso para eliminar hojas de tiempo", + "adjustmentSubmitted": "Ajuste enviado para aprobación", + "adjustmentApplied": "Ajuste aplicado exitosamente", + "adjustmentError": "Fallo al aplicar ajuste" }, "billing": { "title": "Facturación", diff --git a/src/data/translations/fr.json b/src/data/translations/fr.json index 7b4d167..d6e6f2b 100644 --- a/src/data/translations/fr.json +++ b/src/data/translations/fr.json @@ -115,7 +115,7 @@ }, "timesheets": { "title": "Feuilles de Temps", - "subtitle": "Gérer et approuver les entrées de feuilles de temps", + "subtitle": "Gérer et approuver les feuilles de temps des travailleurs", "addTimesheet": "Ajouter une Feuille de Temps", "editTimesheet": "Modifier une Feuille de Temps", "approveTimesheet": "Approuver une Feuille de Temps", @@ -133,7 +133,9 @@ "submitted": "Soumis", "approved": "Approuvé", "rejected": "Rejeté", - "processed": "Traité" + "processed": "Traité", + "pending": "En Attente", + "all": "Tous les Statuts" }, "hoursWorked": "Heures Travaillées", "overtimeHours": "Heures Supplémentaires", @@ -142,7 +144,57 @@ "nightShift": "Quart de Nuit", "weekendShift": "Quart de Fin de Semaine", "adjustments": "Ajustements", - "comments": "Commentaires" + "comments": "Commentaires", + "analytics": "Analytique", + "showAnalytics": "Afficher l'Analytique", + "hideAnalytics": "Masquer l'Analytique", + "totalTimesheets": "Feuilles de Temps Totales", + "pendingReview": "{{count}} en attente de révision", + "thisPeriod": "Cette période", + "validationIssues": "Problèmes de Validation", + "errorsFound": "Erreurs trouvées", + "allValid": "Tout est valide", + "totalValue": "Valeur Totale", + "pendingInvoicing": "Facturation en attente", + "pending": "En Attente", + "awaitingApproval": "En attente d'approbation", + "approved": "Approuvé", + "readyForBilling": "Prêt pour la facturation", + "approvalRate": "Taux d'Approbation", + "searchAndFilter": "Recherche et Filtrage", + "findTimesheetsAdvanced": "Rechercher des feuilles de temps avec recherche avancée", + "results": "{{count}} résultats", + "searchPlaceholder": "Rechercher par travailleur, client ou statut...", + "allStatus": "Tous les Statuts", + "validationErrors": "{{count}} {{errors}} de validation", + "error": "erreur", + "errors": "erreurs", + "exportCsv": "Exporter CSV", + "workerName": "Nom du Travailleur", + "clientName": "Nom du Client", + "bulkImport": "Importation en Vrac", + "detailedEntry": "Entrée Détaillée", + "createSuccess": "Feuille de temps créée avec succès", + "createError": "Échec de la création de la feuille de temps", + "createDetailedSuccess": "Feuille de temps détaillée créée avec succès", + "createDetailedError": "Échec de la création de la feuille de temps détaillée", + "importSuccess": "{{count}} feuilles de temps importées avec succès", + "importError": "Échec de l'importation des feuilles de temps", + "approveSuccess": "Feuille de temps approuvée", + "approveError": "Échec de l'approbation de la feuille de temps", + "rejectSuccess": "Feuille de temps rejetée", + "rejectError": "Échec du rejet de la feuille de temps", + "adjustSuccess": "Feuille de temps ajustée", + "adjustError": "Échec de l'ajustement de la feuille de temps", + "deleteSuccess": "Feuille de temps supprimée", + "deleteError": "Échec de la suppression de la feuille de temps", + "noPermissionApprove": "Vous n'avez pas la permission d'approuver les feuilles de temps", + "noPermissionReject": "Vous n'avez pas la permission de rejeter les feuilles de temps", + "noPermissionEdit": "Vous n'avez pas la permission d'ajuster les feuilles de temps", + "noPermissionDelete": "Vous n'avez pas la permission de supprimer les feuilles de temps", + "adjustmentSubmitted": "Ajustement soumis pour approbation", + "adjustmentApplied": "Ajustement appliqué avec succès", + "adjustmentError": "Échec de l'application de l'ajustement" }, "billing": { "title": "Facturation",