diff --git a/messages/de/dashboard.json b/messages/de/dashboard.json index 33e7ba1..7388acd 100644 --- a/messages/de/dashboard.json +++ b/messages/de/dashboard.json @@ -16,7 +16,8 @@ "billing": "Zur Abrechnung", "emergency_new": "Neue Notdienste", "missing_signatures": "Fehlende Unterschriften", - "sync_conflicts": "Sync-Konflikte" + "sync_conflicts": "Sync-Konflikte", + "time_approvals": "Zeiten zur Freigabe" }, "hints": { "open": "Noch nicht abgerechnet oder storniert", @@ -29,7 +30,8 @@ "billing": "Bereit zur Abrechnung", "emergency_new": "Neu oder zur Prüfung", "missing_signatures": "Unterschrift ausstehend", - "sync_conflicts": "Offline-Änderungen prüfen" + "sync_conflicts": "Offline-Änderungen prüfen", + "time_approvals": "Nachträge und Korrekturen prüfen" }, "open": "Anzeigen" } diff --git a/messages/de/field.json b/messages/de/field.json index ba53ccf..c1b3f21 100644 --- a/messages/de/field.json +++ b/messages/de/field.json @@ -5,7 +5,8 @@ "orders": "Aufträge", "emergency": "Notdienst", "sync": "Sync", - "profile": "Profil" + "profile": "Profil", + "approvalsBadge": "{count} Zeiten zur Freigabe" }, "connection": { "online": "Online", @@ -35,7 +36,13 @@ "noDate": "Ohne Termin", "emergency": "Notdienst", "urgent": "Dringend", - "high": "Hoch" + "high": "Hoch", + "running": "Zeit läuft", + "paused": "Pausiert", + "enRoute": "In Anfahrt", + "startWork": "Arbeit starten", + "pause": "Pause", + "resume": "Weiter" }, "statusGroup": { "new": "Neu", @@ -78,7 +85,17 @@ "saving": "Wird gespeichert …", "blocked": "Vor dem Abschluss noch offen:", "reportNext": "Bericht und Unterschrift folgen im nächsten Schritt.", - "nothing": "Keine Aktion erforderlich." + "nothing": "Keine Aktion erforderlich.", + "stop_day": "Für heute beenden", + "confirmStopDay": "Jetzt für heute beenden", + "stopDayHint": "Die Zeiterfassung endet, der Auftrag bleibt offen und kann morgen fortgesetzt werden.", + "completeLink": "Auftrag abschließen …", + "segment": { + "return_travel": "Rückfahrt starten", + "material_procurement": "Material holen", + "work": "Zurück zur Arbeit" + }, + "currentSegment": "Aktuell: {type}" }, "blocker": { "checklist_item": "Checkliste: {label}", @@ -264,7 +281,8 @@ "en_route": "In Anfahrt", "running": "Läuft", "paused": "Pausiert", - "ended": "Beendet" + "ended": "Beendet", + "manual": "Nachgetragen" }, "running": "läuft", "offlineStarted": "offline gestartet", @@ -277,7 +295,10 @@ "save": "Korrektur speichern", "saved": "Korrektur gespeichert.", "empty": "Noch keine Zeiten erfasst.", - "totalWork": "Arbeitszeit gesamt: {duration}" + "totalWork": "Arbeitszeit gesamt: {duration}", + "propose": "Korrektur vorschlagen", + "totalPending": "Zur Freigabe offen: {duration}", + "ownEntryHint": "Eigene Zeiten korrigieren Sie über „Korrektur vorschlagen“." }, "profile": { "title": "Profil", @@ -287,12 +308,133 @@ "roles": "Rolle", "language": "Sprache", "logout": "Abmelden", - "backoffice": "Zum Büro-Bereich" + "backoffice": "Zum Büro-Bereich", + "myTime": "Meine Zeiten", + "approvals": "Zeiten freigeben" }, "sync": { "title": "Synchronisation", "immediate": "Änderungen werden sofort an das Büro übertragen.", "offlineHint": "Ohne Verbindung können Eingaben noch nicht zwischengespeichert werden – die Offline-Warteschlange folgt.", "status": "Verbindung" + }, + "clock": { + "label": "Laufende Zeiterfassung", + "running": "Läuft", + "paused": "Pausiert", + "enRoute": "Anfahrt", + "openOrder": "Auftrag {number} öffnen", + "pause": "Pause", + "resume": "Weiter", + "stopDay": "Für heute beenden", + "stopDayConfirm": "Beenden?", + "myTime": "Meine Zeiten", + "offline": "offline" + }, + "switch": { + "title": "Andere Zeiterfassung läuft", + "text": "{from} läuft noch. Pausieren und {to} starten?", + "textUnknown": "Auf einem anderen Auftrag läuft noch eine Zeiterfassung. Pausieren und {to} starten?", + "confirm": "Wechseln", + "cancel": "Abbrechen" + }, + "myTime": { + "title": "Meine Zeiten", + "sub": "Ihre Zeiten je Tag über alle Aufträge.", + "today": "Heute", + "yesterday": "Gestern", + "days": "Tag wählen", + "empty": "An diesem Tag sind keine Zeiten erfasst.", + "approved": "Freigegeben", + "pending": "Zur Freigabe", + "dayTotal": "Tagessumme", + "totalApproved": "Freigegeben: {duration}", + "totalPending": "Offen: {duration}", + "add": "Zeit nachtragen", + "running": "läuft", + "badge": { + "manual": "Manuell", + "pending": "Zur Freigabe", + "rejected": "Abgelehnt: {reason}", + "correctionRequested": "Korrektur beantragt", + "correctionRejected": "Korrektur abgelehnt: {reason}", + "corrected": "Korrigiert" + }, + "proposed": "Vorschlag: {from}–{to} · {type}", + "localPending": "Wartet auf Übertragung", + "localTitle": "Noch nicht übertragen", + "form": { + "title": "Zeit nachtragen", + "order": "Auftrag", + "orderPlaceholder": "Auftrag wählen", + "noOrders": "Keine Aufträge der letzten 7 Tage verfügbar.", + "forUser": "Für", + "me": "Mich selbst", + "type": "Art", + "date": "Datum", + "mode": "Erfassung", + "modeRange": "Von – Bis", + "modeDuration": "Dauer", + "from": "Von", + "to": "Bis", + "duration": "Dauer in Minuten", + "quick": "{minutes} min", + "reason": "Begründung", + "reasonHint": "Pflichtangabe, mindestens 3 Zeichen.", + "note": "Notiz (optional)", + "approvalHint": "Wird nach Freigabe durch Teamleiter oder Büro gezählt.", + "directHint": "Für Teammitglieder erfasste Zeiten gelten sofort als freigegeben.", + "save": "Nachtrag speichern", + "saved": "Nachtrag gespeichert – wartet auf Freigabe.", + "savedDirect": "Zeit gespeichert.", + "queued": "Offline gespeichert – wird übertragen, sobald Verbindung besteht.", + "cancel": "Abbrechen" + }, + "correction": { + "title": "Korrektur vorschlagen", + "hint": "Bis zur Freigabe gilt die bisherige Zeit.", + "save": "Vorschlag senden", + "saved": "Korrektur beantragt." + }, + "reasons": { + "forgotStart": "Start vergessen", + "noNetwork": "Kein Netz", + "recordedLater": "Nachträglich erfasst" + }, + "errors": { + "overlap": "Der Zeitraum überschneidet sich mit einer bereits erfassten Zeit.", + "too_old": "Nachtragen ist nur für die letzten 7 Tage möglich.", + "in_future": "Die Zeit darf nicht in der Zukunft liegen.", + "too_long": "Ein Eintrag darf höchstens 16 Stunden lang sein.", + "end_before_start": "Das Ende muss nach dem Beginn liegen.", + "work_order_status": "Für diesen Auftrag können keine Zeiten mehr erfasst werden.", + "not_own_entry": "Sie können nur eigene Zeiten korrigieren.", + "own_entry": "Eigene Zeiten können Sie nicht selbst freigeben.", + "entry_running": "Die Zeit läuft noch." + } + }, + "approvals": { + "title": "Zeiten freigeben", + "sub": "Nachträge und Korrekturen Ihres Teams.", + "empty": "Keine offenen Nachträge oder Korrekturen.", + "link": "Zeiten freigeben", + "count": "{count, plural, =0 {Nichts offen} one {# offen} other {# offen}}", + "manual": "Nachtrag", + "correction": "Korrektur", + "reason": "Begründung", + "note": "Notiz", + "old": "Bisher", + "new": "Neu", + "approve": "Freigeben", + "reject": "Ablehnen", + "rejectReason": "Grund der Ablehnung", + "rejectConfirm": "Ablehnung senden", + "cancel": "Abbrechen", + "approved": "Freigegeben.", + "rejected": "Abgelehnt." + }, + "pendingTime": { + "report": "Hinweis: {duration} nachgetragene Zeit ist noch nicht freigegeben und fehlt im Bericht.", + "proposals": "Hinweis: {count, plural, one {# Zeitkorrektur wartet} other {# Zeitkorrekturen warten}} auf Freigabe." } } diff --git a/messages/de/nav.json b/messages/de/nav.json index efed3cc..f49d785 100644 --- a/messages/de/nav.json +++ b/messages/de/nav.json @@ -16,5 +16,6 @@ "lotse": "Lotse (KI)", "admin": "Admin-Konsole", "openMenu": "Menü öffnen", - "closeMenu": "Menü schließen" + "closeMenu": "Menü schließen", + "timeApprovals": "Zeiten zur Freigabe" } diff --git a/messages/de/notifications.json b/messages/de/notifications.json index 3689c31..5685b8c 100644 --- a/messages/de/notifications.json +++ b/messages/de/notifications.json @@ -45,26 +45,92 @@ "emergency_completed": "Notdiensteinsatz abgeschlossen", "import_ready_for_review": "Import zur Prüfung", "import_failed": "Fehler bei Dokumentverarbeitung", - "sync_failed": "Synchronisationsfehler" + "sync_failed": "Synchronisationsfehler", + "time_approval_requested": "Zeit zur Freigabe", + "time_approved": "Zeit freigegeben", + "time_rejected": "Zeit abgelehnt" }, "events": { - "work_order_assigned": { "title": "Neuer Auftrag {number}", "message": "{title} – {customer} wurde Ihrem Team zugewiesen." }, - "work_order_changed": { "title": "Auftrag {number} geändert", "message": "{title} – {customer}: Angaben wurden aktualisiert." }, - "work_order_cancelled": { "title": "Auftrag {number} storniert", "message": "{title} – {customer} wurde storniert." }, - "work_order_started": { "title": "Auftrag {number} gestartet", "message": "{actor} hat den Einsatz bei {customer} begonnen." }, - "work_order_daily_report_created": { "title": "Tagesbericht zu {number}", "message": "{actor} hat einen Tagesbericht für {customer} erstellt." }, - "work_order_technically_completed": { "title": "Auftrag {number} technisch abgeschlossen", "message": "{title} – {customer}: Einsatz vor Ort beendet." }, - "work_order_signature_missing": { "title": "Unterschrift fehlt: {number}", "message": "{title} – {customer}: Kundenunterschrift liegt nicht vor." }, - "work_order_missing_required": { "title": "Pflichtangaben fehlen: {number}", "message": "{title} – {customer}: Dokumentation unvollständig." }, - "work_order_released_for_billing": { "title": "Auftrag {number} zur Abrechnung", "message": "{title} – {customer} ist bereit zur Abrechnung." }, - "report_submitted": { "title": "Bericht zur Prüfung: {number}", "message": "{actor} hat einen Bericht zu {title} eingereicht." }, - "report_approved": { "title": "Bericht freigegeben: {number}", "message": "Der Bericht zu {title} wurde freigegeben." }, - "report_rejected": { "title": "Bericht zurückgewiesen: {number}", "message": "Der Bericht zu {title} braucht eine Korrektur." }, - "emergency_created": { "title": "Notdiensteinsatz {number} erstellt", "message": "{actor} hat einen Notdiensteinsatz bei {customer} angelegt." }, - "emergency_completed": { "title": "Notdiensteinsatz {number} abgeschlossen", "message": "{customer}: zur Prüfung und Abrechnung." }, - "import_ready_for_review": { "title": "Import zur Prüfung bereit", "message": "Die Daten aus {fileName} liegen zur Prüfung vor." }, - "import_failed": { "title": "Fehler bei Dokumentverarbeitung", "message": "{fileName} konnte nicht verarbeitet werden." }, - "sync_failed": { "title": "Synchronisation fehlgeschlagen", "message": "Eine Änderung konnte nicht übernommen werden ({reason})." } + "work_order_assigned": { + "title": "Neuer Auftrag {number}", + "message": "{title} – {customer} wurde Ihrem Team zugewiesen." + }, + "work_order_changed": { + "title": "Auftrag {number} geändert", + "message": "{title} – {customer}: Angaben wurden aktualisiert." + }, + "work_order_cancelled": { + "title": "Auftrag {number} storniert", + "message": "{title} – {customer} wurde storniert." + }, + "work_order_started": { + "title": "Auftrag {number} gestartet", + "message": "{actor} hat den Einsatz bei {customer} begonnen." + }, + "work_order_daily_report_created": { + "title": "Tagesbericht zu {number}", + "message": "{actor} hat einen Tagesbericht für {customer} erstellt." + }, + "work_order_technically_completed": { + "title": "Auftrag {number} technisch abgeschlossen", + "message": "{title} – {customer}: Einsatz vor Ort beendet." + }, + "work_order_signature_missing": { + "title": "Unterschrift fehlt: {number}", + "message": "{title} – {customer}: Kundenunterschrift liegt nicht vor." + }, + "work_order_missing_required": { + "title": "Pflichtangaben fehlen: {number}", + "message": "{title} – {customer}: Dokumentation unvollständig." + }, + "work_order_released_for_billing": { + "title": "Auftrag {number} zur Abrechnung", + "message": "{title} – {customer} ist bereit zur Abrechnung." + }, + "report_submitted": { + "title": "Bericht zur Prüfung: {number}", + "message": "{actor} hat einen Bericht zu {title} eingereicht." + }, + "report_approved": { + "title": "Bericht freigegeben: {number}", + "message": "Der Bericht zu {title} wurde freigegeben." + }, + "report_rejected": { + "title": "Bericht zurückgewiesen: {number}", + "message": "Der Bericht zu {title} braucht eine Korrektur." + }, + "emergency_created": { + "title": "Notdiensteinsatz {number} erstellt", + "message": "{actor} hat einen Notdiensteinsatz bei {customer} angelegt." + }, + "emergency_completed": { + "title": "Notdiensteinsatz {number} abgeschlossen", + "message": "{customer}: zur Prüfung und Abrechnung." + }, + "import_ready_for_review": { + "title": "Import zur Prüfung bereit", + "message": "Die Daten aus {fileName} liegen zur Prüfung vor." + }, + "import_failed": { + "title": "Fehler bei Dokumentverarbeitung", + "message": "{fileName} konnte nicht verarbeitet werden." + }, + "sync_failed": { + "title": "Synchronisation fehlgeschlagen", + "message": "Eine Änderung konnte nicht übernommen werden ({reason})." + }, + "time_approval_requested": { + "title": "Zeit zur Freigabe: {number}", + "message": "{actor} hat eine Zeit zu {number} nachgetragen oder eine Korrektur beantragt." + }, + "time_approved": { + "title": "Zeit freigegeben: {number}", + "message": "{actor} hat Ihre Zeit zu {number} freigegeben." + }, + "time_rejected": { + "title": "Zeit abgelehnt: {number}", + "message": "{actor} hat Ihre Zeit zu {number} abgelehnt: {reason}" + } }, "fallback": { "unknown": "unbekannt", diff --git a/messages/de/offline.json b/messages/de/offline.json index 07a4bbb..89336f8 100644 --- a/messages/de/offline.json +++ b/messages/de/offline.json @@ -89,7 +89,10 @@ "upload": "Die Datei wurde nicht angenommen (Format oder Größe).", "unauthorized": "Anmeldung abgelaufen – bitte neu anmelden.", "network": "Keine Verbindung – wird automatisch erneut versucht.", - "internal": "Technischer Fehler – wird automatisch erneut versucht." + "internal": "Technischer Fehler – wird automatisch erneut versucht.", + "timeOverlap": "Der Zeitnachtrag überschneidet sich mit einer bereits erfassten Zeit und wurde nicht übernommen.", + "timeWindow": "Der Zeitnachtrag liegt außerhalb der erlaubten 7 Tage, in der Zukunft oder ist zu lang und wurde nicht übernommen.", + "otherSession": "Auf einem anderen Auftrag lief noch eine Zeiterfassung – der Start wurde nicht übernommen. Bitte erneut starten und wechseln." }, "view": { "title": "Offline-Ansicht", diff --git a/messages/de/workOrders.json b/messages/de/workOrders.json index 0cb6433..94eb659 100644 --- a/messages/de/workOrders.json +++ b/messages/de/workOrders.json @@ -237,7 +237,21 @@ "material_procurement": "Materialbeschaffung", "return_travel": "Rückfahrt", "interruption": "Unterbrechung" - } + }, + "pendingMinutes": "Zur Freigabe offen: {minutes} min", + "approvedMinutes": "Freigegeben", + "badgeManual": "Manuell", + "badgePending": "Zur Freigabe", + "badgeRejected": "Abgelehnt: {reason}", + "badgeCorrection": "Korrektur beantragt: {from}–{to}", + "reason": "Begründung: {reason}", + "manualSession": "Nachgetragen", + "approve": "Freigeben", + "reject": "Ablehnen", + "rejectReason": "Grund", + "approved": "Freigegeben.", + "rejected": "Abgelehnt.", + "openApprovals": "Alle offenen Freigaben" }, "photos": { "empty": "Noch keine Fotos.", @@ -460,8 +474,35 @@ "running_session": "Ein Einsatz läuft noch", "field": { "signature": "Unterschrift fehlt", - "approved_completion_report": "Abschlussbericht ist noch nicht freigegeben" + "approved_completion_report": "Abschlussbericht ist noch nicht freigegeben", + "pending_time_entries": "Zeiten warten noch auf Freigabe" } }, - "saved": "Gespeichert." + "saved": "Gespeichert.", + "timeApprovals": { + "crumb": "Aufträge", + "title": "Zeiten zur Freigabe", + "sub": "Nachträge und Korrekturen der Monteure. Erst nach Freigabe zählen sie für Bericht und Abrechnung.", + "empty": "Keine offenen Nachträge oder Korrekturen.", + "employee": "Mitarbeiter", + "order": "Auftrag", + "date": "Datum", + "range": "Von–Bis", + "duration": "Dauer", + "type": "Art", + "reason": "Begründung", + "kindManual": "Nachtrag", + "kindCorrection": "Korrektur", + "oldNew": "{old} → {new}", + "select": "Auswählen", + "selectAll": "Alle auswählen", + "approveSelected": "Ausgewählte freigeben", + "approve": "Freigeben", + "reject": "Ablehnen", + "rejectReason": "Grund der Ablehnung", + "approved": "Freigegeben.", + "bulkApproved": "{ok} freigegeben, {failed} nicht möglich.", + "rejected": "Abgelehnt.", + "count": "{count, plural, one {# offener Eintrag} other {# offene Einträge}}" + } } diff --git a/messages/en/dashboard.json b/messages/en/dashboard.json index eee1199..3f36189 100644 --- a/messages/en/dashboard.json +++ b/messages/en/dashboard.json @@ -16,7 +16,8 @@ "billing": "Ready for billing", "emergency_new": "New emergencies", "missing_signatures": "Missing signatures", - "sync_conflicts": "Sync conflicts" + "sync_conflicts": "Sync conflicts", + "time_approvals": "Time approvals" }, "hints": { "open": "Not yet billed or cancelled", @@ -29,7 +30,8 @@ "billing": "Ready to invoice", "emergency_new": "New or in review", "missing_signatures": "Signature pending", - "sync_conflicts": "Review offline changes" + "sync_conflicts": "Review offline changes", + "time_approvals": "Review added time and corrections" }, "open": "Show" } diff --git a/messages/en/field.json b/messages/en/field.json index d8a5456..57a6825 100644 --- a/messages/en/field.json +++ b/messages/en/field.json @@ -5,7 +5,8 @@ "orders": "Orders", "emergency": "Emergency", "sync": "Sync", - "profile": "Profile" + "profile": "Profile", + "approvalsBadge": "{count} time entries to approve" }, "connection": { "online": "Online", @@ -35,7 +36,13 @@ "noDate": "No date", "emergency": "Emergency", "urgent": "Urgent", - "high": "High" + "high": "High", + "running": "Time running", + "paused": "Paused", + "enRoute": "On the way", + "startWork": "Start work", + "pause": "Pause", + "resume": "Resume" }, "statusGroup": { "new": "New", @@ -78,7 +85,17 @@ "saving": "Saving …", "blocked": "Still missing before completion:", "reportNext": "Report and signature follow in the next step.", - "nothing": "No action required." + "nothing": "No action required.", + "stop_day": "Stop for today", + "confirmStopDay": "Stop for today now", + "stopDayHint": "Time recording ends, the order stays open and can be continued tomorrow.", + "completeLink": "Complete order …", + "segment": { + "return_travel": "Start return trip", + "material_procurement": "Fetch material", + "work": "Back to work" + }, + "currentSegment": "Current: {type}" }, "blocker": { "checklist_item": "Checklist: {label}", @@ -264,7 +281,8 @@ "en_route": "On the way", "running": "Running", "paused": "Paused", - "ended": "Ended" + "ended": "Ended", + "manual": "Recorded later" }, "running": "running", "offlineStarted": "started offline", @@ -277,7 +295,10 @@ "save": "Save correction", "saved": "Correction saved.", "empty": "No time recorded yet.", - "totalWork": "Total working time: {duration}" + "totalWork": "Total working time: {duration}", + "propose": "Propose correction", + "totalPending": "Awaiting approval: {duration}", + "ownEntryHint": "Correct your own time via “Propose correction”." }, "profile": { "title": "Profile", @@ -287,12 +308,133 @@ "roles": "Role", "language": "Language", "logout": "Sign out", - "backoffice": "Go to office area" + "backoffice": "Go to office area", + "myTime": "My time", + "approvals": "Approve time" }, "sync": { "title": "Synchronisation", "immediate": "Changes are sent to the office immediately.", "offlineHint": "Without a connection, entries cannot be buffered yet – the offline queue will follow.", "status": "Connection" + }, + "clock": { + "label": "Running time recording", + "running": "Running", + "paused": "Paused", + "enRoute": "Travel", + "openOrder": "Open order {number}", + "pause": "Pause", + "resume": "Resume", + "stopDay": "Stop for today", + "stopDayConfirm": "Stop?", + "myTime": "My time", + "offline": "offline" + }, + "switch": { + "title": "Another time recording is running", + "text": "{from} is still running. Pause it and start {to}?", + "textUnknown": "Time is still running on another order. Pause it and start {to}?", + "confirm": "Switch", + "cancel": "Cancel" + }, + "myTime": { + "title": "My time", + "sub": "Your time per day across all orders.", + "today": "Today", + "yesterday": "Yesterday", + "days": "Choose day", + "empty": "No time recorded on this day.", + "approved": "Approved", + "pending": "Awaiting approval", + "dayTotal": "Day total", + "totalApproved": "Approved: {duration}", + "totalPending": "Open: {duration}", + "add": "Add time", + "running": "running", + "badge": { + "manual": "Manual", + "pending": "Awaiting approval", + "rejected": "Rejected: {reason}", + "correctionRequested": "Correction requested", + "correctionRejected": "Correction rejected: {reason}", + "corrected": "Corrected" + }, + "proposed": "Proposal: {from}–{to} · {type}", + "localPending": "Waiting to be sent", + "localTitle": "Not sent yet", + "form": { + "title": "Add time", + "order": "Order", + "orderPlaceholder": "Choose order", + "noOrders": "No orders from the last 7 days available.", + "forUser": "For", + "me": "Myself", + "type": "Type", + "date": "Date", + "mode": "Entry", + "modeRange": "From – to", + "modeDuration": "Duration", + "from": "From", + "to": "To", + "duration": "Duration in minutes", + "quick": "{minutes} min", + "reason": "Reason", + "reasonHint": "Required, at least 3 characters.", + "note": "Note (optional)", + "approvalHint": "Counts once approved by the team lead or office.", + "directHint": "Time recorded for team members is approved immediately.", + "save": "Save entry", + "saved": "Entry saved – awaiting approval.", + "savedDirect": "Time saved.", + "queued": "Saved offline – will be sent once connected.", + "cancel": "Cancel" + }, + "correction": { + "title": "Propose correction", + "hint": "The current time stays valid until approved.", + "save": "Send proposal", + "saved": "Correction requested." + }, + "reasons": { + "forgotStart": "Forgot to start", + "noNetwork": "No network", + "recordedLater": "Recorded afterwards" + }, + "errors": { + "overlap": "The period overlaps with time already recorded.", + "too_old": "Time can only be added for the last 7 days.", + "in_future": "The time must not be in the future.", + "too_long": "An entry may be at most 16 hours long.", + "end_before_start": "The end must be after the start.", + "work_order_status": "Time can no longer be recorded for this order.", + "not_own_entry": "You can only correct your own time.", + "own_entry": "You cannot approve your own time.", + "entry_running": "The time is still running." + } + }, + "approvals": { + "title": "Approve time", + "sub": "Added entries and corrections of your team.", + "empty": "No open entries or corrections.", + "link": "Approve time", + "count": "{count, plural, =0 {Nothing open} one {# open} other {# open}}", + "manual": "Added entry", + "correction": "Correction", + "reason": "Reason", + "note": "Note", + "old": "Before", + "new": "New", + "approve": "Approve", + "reject": "Reject", + "rejectReason": "Reason for rejection", + "rejectConfirm": "Send rejection", + "cancel": "Cancel", + "approved": "Approved.", + "rejected": "Rejected." + }, + "pendingTime": { + "report": "Note: {duration} of added time is not approved yet and missing from the report.", + "proposals": "Note: {count, plural, one {# time correction is} other {# time corrections are}} awaiting approval." } } diff --git a/messages/en/nav.json b/messages/en/nav.json index 91f4ba6..5e97423 100644 --- a/messages/en/nav.json +++ b/messages/en/nav.json @@ -16,5 +16,6 @@ "lotse": "Lotse (AI)", "admin": "Admin console", "openMenu": "Open menu", - "closeMenu": "Close menu" + "closeMenu": "Close menu", + "timeApprovals": "Time approvals" } diff --git a/messages/en/notifications.json b/messages/en/notifications.json index 3206fdf..13ca202 100644 --- a/messages/en/notifications.json +++ b/messages/en/notifications.json @@ -45,26 +45,92 @@ "emergency_completed": "Emergency call-out completed", "import_ready_for_review": "Import ready for review", "import_failed": "Document processing failed", - "sync_failed": "Sync failed" + "sync_failed": "Sync failed", + "time_approval_requested": "Time to approve", + "time_approved": "Time approved", + "time_rejected": "Time rejected" }, "events": { - "work_order_assigned": { "title": "New work order {number}", "message": "{title} – {customer} was assigned to your team." }, - "work_order_changed": { "title": "Work order {number} changed", "message": "{title} – {customer}: details were updated." }, - "work_order_cancelled": { "title": "Work order {number} cancelled", "message": "{title} – {customer} was cancelled." }, - "work_order_started": { "title": "Work order {number} started", "message": "{actor} started work at {customer}." }, - "work_order_daily_report_created": { "title": "Daily report for {number}", "message": "{actor} created a daily report for {customer}." }, - "work_order_technically_completed": { "title": "Work order {number} technically completed", "message": "{title} – {customer}: on-site work finished." }, - "work_order_signature_missing": { "title": "Signature missing: {number}", "message": "{title} – {customer}: no customer signature." }, - "work_order_missing_required": { "title": "Required information missing: {number}", "message": "{title} – {customer}: documentation incomplete." }, - "work_order_released_for_billing": { "title": "Work order {number} ready for billing", "message": "{title} – {customer} is ready for billing." }, - "report_submitted": { "title": "Report for review: {number}", "message": "{actor} submitted a report for {title}." }, - "report_approved": { "title": "Report approved: {number}", "message": "The report for {title} was approved." }, - "report_rejected": { "title": "Report rejected: {number}", "message": "The report for {title} needs a correction." }, - "emergency_created": { "title": "Emergency call-out {number} created", "message": "{actor} created an emergency call-out at {customer}." }, - "emergency_completed": { "title": "Emergency call-out {number} completed", "message": "{customer}: ready for review and billing." }, - "import_ready_for_review": { "title": "Import ready for review", "message": "The data from {fileName} is ready for review." }, - "import_failed": { "title": "Document processing failed", "message": "{fileName} could not be processed." }, - "sync_failed": { "title": "Sync failed", "message": "A change could not be applied ({reason})." } + "work_order_assigned": { + "title": "New work order {number}", + "message": "{title} – {customer} was assigned to your team." + }, + "work_order_changed": { + "title": "Work order {number} changed", + "message": "{title} – {customer}: details were updated." + }, + "work_order_cancelled": { + "title": "Work order {number} cancelled", + "message": "{title} – {customer} was cancelled." + }, + "work_order_started": { + "title": "Work order {number} started", + "message": "{actor} started work at {customer}." + }, + "work_order_daily_report_created": { + "title": "Daily report for {number}", + "message": "{actor} created a daily report for {customer}." + }, + "work_order_technically_completed": { + "title": "Work order {number} technically completed", + "message": "{title} – {customer}: on-site work finished." + }, + "work_order_signature_missing": { + "title": "Signature missing: {number}", + "message": "{title} – {customer}: no customer signature." + }, + "work_order_missing_required": { + "title": "Required information missing: {number}", + "message": "{title} – {customer}: documentation incomplete." + }, + "work_order_released_for_billing": { + "title": "Work order {number} ready for billing", + "message": "{title} – {customer} is ready for billing." + }, + "report_submitted": { + "title": "Report for review: {number}", + "message": "{actor} submitted a report for {title}." + }, + "report_approved": { + "title": "Report approved: {number}", + "message": "The report for {title} was approved." + }, + "report_rejected": { + "title": "Report rejected: {number}", + "message": "The report for {title} needs a correction." + }, + "emergency_created": { + "title": "Emergency call-out {number} created", + "message": "{actor} created an emergency call-out at {customer}." + }, + "emergency_completed": { + "title": "Emergency call-out {number} completed", + "message": "{customer}: ready for review and billing." + }, + "import_ready_for_review": { + "title": "Import ready for review", + "message": "The data from {fileName} is ready for review." + }, + "import_failed": { + "title": "Document processing failed", + "message": "{fileName} could not be processed." + }, + "sync_failed": { + "title": "Sync failed", + "message": "A change could not be applied ({reason})." + }, + "time_approval_requested": { + "title": "Time to approve: {number}", + "message": "{actor} added time or requested a correction for {number}." + }, + "time_approved": { + "title": "Time approved: {number}", + "message": "{actor} approved your time for {number}." + }, + "time_rejected": { + "title": "Time rejected: {number}", + "message": "{actor} rejected your time for {number}: {reason}" + } }, "fallback": { "unknown": "unknown", diff --git a/messages/en/offline.json b/messages/en/offline.json index 2a982be..b2ec4a9 100644 --- a/messages/en/offline.json +++ b/messages/en/offline.json @@ -89,7 +89,10 @@ "upload": "The file was not accepted (format or size).", "unauthorized": "Your session expired – please sign in again.", "network": "No connection – will retry automatically.", - "internal": "Technical error – will retry automatically." + "internal": "Technical error – will retry automatically.", + "timeOverlap": "The added time overlaps with time already recorded and was not accepted.", + "timeWindow": "The added time is outside the allowed 7 days, in the future or too long and was not accepted.", + "otherSession": "Time was still running on another order – the start was not accepted. Please start again and switch." }, "view": { "title": "Offline view", diff --git a/messages/en/workOrders.json b/messages/en/workOrders.json index 166e853..9ff39e2 100644 --- a/messages/en/workOrders.json +++ b/messages/en/workOrders.json @@ -237,7 +237,21 @@ "material_procurement": "Material procurement", "return_travel": "Return travel", "interruption": "Interruption" - } + }, + "pendingMinutes": "Awaiting approval: {minutes} min", + "approvedMinutes": "Approved", + "badgeManual": "Manual", + "badgePending": "Awaiting approval", + "badgeRejected": "Rejected: {reason}", + "badgeCorrection": "Correction requested: {from}–{to}", + "reason": "Reason: {reason}", + "manualSession": "Recorded later", + "approve": "Approve", + "reject": "Reject", + "rejectReason": "Reason", + "approved": "Approved.", + "rejected": "Rejected.", + "openApprovals": "All open approvals" }, "photos": { "empty": "No photos yet.", @@ -460,8 +474,35 @@ "running_session": "A work session is still running", "field": { "signature": "Signature missing", - "approved_completion_report": "Completion report not approved yet" + "approved_completion_report": "Completion report not approved yet", + "pending_time_entries": "Time entries are still awaiting approval" } }, - "saved": "Saved." + "saved": "Saved.", + "timeApprovals": { + "crumb": "Work orders", + "title": "Time approvals", + "sub": "Added time and corrections by technicians. They count for reports and billing only after approval.", + "empty": "No open entries or corrections.", + "employee": "Employee", + "order": "Order", + "date": "Date", + "range": "From–to", + "duration": "Duration", + "type": "Type", + "reason": "Reason", + "kindManual": "Added entry", + "kindCorrection": "Correction", + "oldNew": "{old} → {new}", + "select": "Select", + "selectAll": "Select all", + "approveSelected": "Approve selected", + "approve": "Approve", + "reject": "Reject", + "rejectReason": "Reason for rejection", + "approved": "Approved.", + "bulkApproved": "{ok} approved, {failed} not possible.", + "rejected": "Rejected.", + "count": "{count, plural, one {# open entry} other {# open entries}}" + } } diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx index 0261dee..d828066 100644 --- a/src/app/(app)/dashboard/page.tsx +++ b/src/app/(app)/dashboard/page.tsx @@ -12,6 +12,7 @@ import { Receipt, RefreshCw, Siren, + Timer, Wrench, type LucideIcon, } from "lucide-react"; @@ -27,7 +28,7 @@ import { listOrderTypes } from "@/server/services/work-orders/settings"; type SP = Record; -const TILES: { key: Preset | "sync_conflicts"; icon: LucideIcon; tone: string }[] = [ +const TILES: { key: Preset | "sync_conflicts" | "time_approvals"; icon: LucideIcon; tone: string }[] = [ { key: "open", icon: ClipboardList, tone: "var(--ui-primary)" }, { key: "today", icon: CalendarDays, tone: "var(--info)" }, { key: "running", icon: Wrench, tone: "var(--ui-accent)" }, @@ -39,6 +40,7 @@ const TILES: { key: Preset | "sync_conflicts"; icon: LucideIcon; tone: string }[ { key: "emergency_new", icon: Siren, tone: "var(--risk)" }, { key: "missing_signatures", icon: PenLine, tone: "var(--warn)" }, { key: "sync_conflicts", icon: RefreshCw, tone: "var(--risk)" }, + { key: "time_approvals", icon: Timer, tone: "var(--warn)" }, ]; /** Backoffice dashboard (spec §21). Field roles are sent to the mobile start page. */ @@ -142,9 +144,9 @@ export default async function DashboardPage({ searchParams }: { searchParams: Pr