From 6fc0e824e13f1a7f82bc1b4ac3c85a90d26c7bc4 Mon Sep 17 00:00:00 2001 From: Florian Date: Sun, 12 Apr 2026 01:04:46 +0200 Subject: [PATCH 1/4] added decimal or clock time visualisation # Conflicts: # resources/js/Components/TimestampTypeBadge.vue # resources/js/Pages/Settings/General/Edit.vue --- .../Settings/GeneralController.php | 3 ++ app/Http/Middleware/HandleInertiaRequests.php | 2 + .../Requests/UpdateGeneralSettingsRequest.php | 2 + app/Jobs/MenubarRefresh.php | 9 +++- app/Services/TimeFormatService.php | 21 ++++++++ app/Settings/GeneralSettings.php | 2 + ...ime_display_format_to_general_settings.php | 18 +++++++ lang/da/app.php | 4 ++ lang/de/app.php | 4 ++ lang/en/app.php | 4 ++ lang/fr/app.php | 4 ++ lang/it/app.php | 4 ++ lang/pt_BR/app.php | 4 ++ lang/zh_CN/app.php | 4 ++ .../js/Components/TimestampTypeBadge.vue | 11 ++-- resources/js/Layouts/BasicLayout.vue | 14 ++++- resources/js/Layouts/DefaultLayout.vue | 14 ++++- resources/js/Pages/Overview/Month/Show.vue | 7 +-- resources/js/Pages/Overview/Year/Show.vue | 7 +-- resources/js/Pages/Settings/General/Edit.vue | 36 +++++++++++-- resources/js/lib/utils.ts | 41 ++++++++++++++- resources/js/types/index.d.ts | 1 + .../Feature/GeneralTimeDisplayFormatTest.php | 51 +++++++++++++++++++ tests/Unit/TimeFormatServiceTest.php | 13 +++++ 24 files changed, 255 insertions(+), 25 deletions(-) create mode 100644 app/Services/TimeFormatService.php create mode 100644 database/settings/2026_04_11_195700_add_time_display_format_to_general_settings.php create mode 100644 tests/Feature/GeneralTimeDisplayFormatTest.php create mode 100644 tests/Unit/TimeFormatServiceTest.php diff --git a/app/Http/Controllers/Settings/GeneralController.php b/app/Http/Controllers/Settings/GeneralController.php index 10a9c75..e6d56ea 100644 --- a/app/Http/Controllers/Settings/GeneralController.php +++ b/app/Http/Controllers/Settings/GeneralController.php @@ -10,6 +10,7 @@ use App\Http\Requests\UpdateGeneralSettingsRequest; use App\Http\Requests\UpdateLocaleRequest; use App\Jobs\CalculateWeekBalance; +use App\Services\TimeFormatService; use App\Settings\GeneralSettings; use App\Settings\ProjectSettings; use DateTimeZone; @@ -38,6 +39,7 @@ public function edit(GeneralSettings $settings) 'timezones' => DateTimeZone::listIdentifiers(), 'timezone' => $settings->timezone, 'defaultOverview' => $settings->default_overview, + 'timeDisplayFormat' => $settings->time_display_format ?? TimeFormatService::CLOCK, ]); } @@ -53,6 +55,7 @@ public function update(UpdateGeneralSettingsRequest $request, GeneralSettings $s $settings->appActivityTracking = $data['appActivityTracking']; $settings->timezone = $data['timezone']; $settings->default_overview = $data['default_overview'] ?? 'week'; + $settings->time_display_format = $data['time_display_format'] ?? TimeFormatService::CLOCK; if ($data['theme'] !== $settings->theme ?? SystemThemesEnum::SYSTEM->value) { $settings->theme = $data['theme']; diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 525dd8e..288c9d3 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -4,6 +4,7 @@ namespace App\Http\Middleware; +use App\Services\TimeFormatService; use App\Services\TimestampService; use App\Settings\GeneralSettings; use Illuminate\Http\Request; @@ -42,6 +43,7 @@ public function share(Request $request): array 'js_locale' => str_replace('_', '-', $settings->locale ?? config('app.fallback_locale')), 'locale' => $settings->locale ?? config('app.fallback_locale'), 'timezone' => $settings->timezone ?? config('app.timezone'), + 'time_display_format' => $settings->time_display_format ?? TimeFormatService::CLOCK, 'app_version' => config('nativephp.version'), 'date' => now()->format('Y-m-d'), 'recording' => (bool) TimestampService::getCurrentType(), diff --git a/app/Http/Requests/UpdateGeneralSettingsRequest.php b/app/Http/Requests/UpdateGeneralSettingsRequest.php index 71cc80d..d8876e5 100644 --- a/app/Http/Requests/UpdateGeneralSettingsRequest.php +++ b/app/Http/Requests/UpdateGeneralSettingsRequest.php @@ -4,6 +4,7 @@ namespace App\Http\Requests; +use App\Services\TimeFormatService; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -35,6 +36,7 @@ public function rules(): array 'appActivityTracking' => ['required', 'boolean'], 'timezone' => ['required', 'string', 'timezone'], 'default_overview' => ['required', Rule::in(['day', 'week', 'month', 'year'])], + 'time_display_format' => ['required', Rule::in([TimeFormatService::CLOCK, TimeFormatService::DECIMAL])], ]; } } diff --git a/app/Jobs/MenubarRefresh.php b/app/Jobs/MenubarRefresh.php index 0fbc384..3ca1976 100644 --- a/app/Jobs/MenubarRefresh.php +++ b/app/Jobs/MenubarRefresh.php @@ -6,8 +6,10 @@ use App\Enums\TimestampTypeEnum; use App\Services\LocaleService; +use App\Services\TimeFormatService; use App\Services\TimestampService; use App\Services\TrayIconService; +use App\Settings\GeneralSettings; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Queue\Queueable; use Native\Desktop\Facades\MenuBar; @@ -23,6 +25,7 @@ public function handle(): void { try { new LocaleService; + $settings = resolve(GeneralSettings::class); $currentType = TimestampService::getCurrentType(); if ($currentType === TimestampTypeEnum::WORK) { @@ -39,8 +42,10 @@ public function handle(): void return; } - MenuBar::tooltip(gmdate('G:i', (int) $time)); - MenuBar::label(gmdate('G:i', (int) $time)); + $formattedTime = TimeFormatService::formatDuration($time, $settings->time_display_format ?? TimeFormatService::CLOCK); + + MenuBar::tooltip($formattedTime); + MenuBar::label($formattedTime); } catch (\Throwable) { return; } diff --git a/app/Services/TimeFormatService.php b/app/Services/TimeFormatService.php new file mode 100644 index 0000000..085ba56 --- /dev/null +++ b/app/Services/TimeFormatService.php @@ -0,0 +1,21 @@ +migrator->add('general.time_display_format', 'clock'); + } + + public function down(): void + { + $this->migrator->delete('general.time_display_format'); + } +}; diff --git a/lang/da/app.php b/lang/da/app.php index 3a204a3..e304991 100644 --- a/lang/da/app.php +++ b/lang/da/app.php @@ -54,9 +54,11 @@ 'chinese' => 'Kinesisk', 'choose how fractional days are rounded.' => 'Vælg, hvordan brøkdelsdage rundes.', 'choose the appearance of the application.' => 'Vælg applikationens udseende.', + 'choose how durations are shown throughout the app. decimal hours are useful for copying time into external systems.' => 'Vælg, hvordan varigheder vises i appen. Decimaltimer er nyttige, når tid skal kopieres til eksterne systemer.', 'choose whether to show vacation steps' => 'Vælg, om ferietrin skal vises.', 'choose your mode' => 'Vælg din tilstand.', 'click "export" at the top of the table and choose "save as csv".' => 'Klik på "Eksporter" øverst i tabellen og vælg "Gem som CSV".', + 'clock (7:30)' => 'Klokkeslæt (7:30)', 'close' => 'Luk', 'color' => 'Farve', 'confirm' => 'Bekræft', @@ -89,6 +91,7 @@ 'day' => 'Dag', 'day equivalent' => 'Dagsækvivalent', 'days' => 'Dage', + 'decimal hours (7.50)' => 'Decimaltimer (7.50)', 'default annual entitlement' => 'Standard årlig tildeling', 'default overview' => 'Standardoversigt', 'default: :value days' => 'Standard: :value dage', @@ -341,6 +344,7 @@ 'this shortcut is not supported.' => 'Denne genvej understøttes ikke.', 'time' => 'Tid', 'time balance' => 'Tidssaldo', + 'time display' => 'Tidsvisning', 'time span' => 'Tidsperiode', 'timezone' => 'Tidszone', 'to import data from clockify, you must first export it. Simply follow the step-by-step instructions below.' => 'For at importere data fra Clockify skal du først eksportere dem. Følg blot de trinvise instruktioner nedenfor.', diff --git a/lang/de/app.php b/lang/de/app.php index 9fd000e..3bf5808 100644 --- a/lang/de/app.php +++ b/lang/de/app.php @@ -54,9 +54,11 @@ 'chinese' => 'Chinesisch', 'choose how fractional days are rounded.' => 'Legt fest, in welchen Abstufungen Urlaubstage gerundet werden.', 'choose the appearance of the application.' => 'Wähle das Erscheinungsbild der Anwendung.', + 'choose how durations are shown throughout the app. decimal hours are useful for copying time into external systems.' => 'Lege fest, wie Zeitdauern in der App angezeigt werden. Dezimalstunden sind hilfreich, wenn du Zeiten in externe Systeme überträgst.', 'choose whether to show vacation steps' => 'Wähle, ob wir Urlaubsschritte zeigen sollen.', 'choose your mode' => 'Wähle deinen Modus.', 'click "export" at the top of the table and choose "save as csv".' => 'Klicken Sie oben in der Tabelle auf „Export“ und wählen Sie „Als CSV speichern“.', + 'clock (7:30)' => 'Uhrzeit (7:30)', 'close' => 'Schließen', 'color' => 'Farbe', 'confirm' => 'Bestätigen', @@ -89,6 +91,7 @@ 'day' => 'Tag', 'day equivalent' => 'Tagesäquivalent', 'days' => 'Tage', + 'decimal hours (7.50)' => 'Dezimalstunden (7.50)', 'default annual entitlement' => 'Standardjahreskontingent', 'default overview' => 'Standard-Übersicht', 'default: :value days' => 'Standardwert: :value Tage', @@ -341,6 +344,7 @@ 'this shortcut is not supported.' => 'Diese Tastenkombination wird nicht unterstützt.', 'time' => 'Uhrzeit', 'time balance' => 'Arbeitszeitbilanz', + 'time display' => 'Zeitanzeige', 'time span' => 'Zeitspanne', 'timezone' => 'Zeitzone', 'to import data from clockify, you must first export it. Simply follow the step-by-step instructions below.' => 'Um Daten aus Clockify zu importieren, müssen Sie diese zunächst exportieren. Folgen Sie einfach der nachfolgenden Schritt-für-Schritt-Anleitung.', diff --git a/lang/en/app.php b/lang/en/app.php index c46efd6..79bd5fd 100644 --- a/lang/en/app.php +++ b/lang/en/app.php @@ -54,9 +54,11 @@ 'chinese' => 'Chinese', 'choose how fractional days are rounded.' => 'Choose how fractional days are rounded.', 'choose the appearance of the application.' => 'Choose the appearance of the application.', + 'choose how durations are shown throughout the app. decimal hours are useful for copying time into external systems.' => 'Choose how durations are shown throughout the app. Decimal hours are useful for copying time into external systems.', 'choose whether to show vacation steps' => 'Choose whether to show vacation steps.', 'choose your mode' => 'Choose your mode.', 'click "export" at the top of the table and choose "save as csv".' => 'Click "Export" at the top of the table and choose "Save as CSV".', + 'clock (7:30)' => 'Clock (7:30)', 'close' => 'Close', 'color' => 'Color', 'confirm' => 'Confirm', @@ -89,6 +91,7 @@ 'day' => 'Day', 'day equivalent' => 'Day equivalent', 'days' => 'Days', + 'decimal hours (7.50)' => 'Decimal hours (7.50)', 'default annual entitlement' => 'Default annual entitlement', 'default overview' => 'Default overview', 'default: :value days' => 'Default: :value days', @@ -341,6 +344,7 @@ 'this shortcut is not supported.' => 'This shortcut is not supported.', 'time' => 'Time', 'time balance' => 'Time Balance', + 'time display' => 'Time display', 'time span' => 'Time span', 'timezone' => 'Timezone', 'to import data from clockify, you must first export it. Simply follow the step-by-step instructions below.' => 'To import data from Clockify, you must first export it. Simply follow the step-by-step instructions below.', diff --git a/lang/fr/app.php b/lang/fr/app.php index 7f48c87..bd16277 100644 --- a/lang/fr/app.php +++ b/lang/fr/app.php @@ -54,9 +54,11 @@ 'chinese' => 'Chinois', 'choose how fractional days are rounded.' => 'Détermine comment les fractions de journée sont arrondies.', 'choose the appearance of the application.' => 'Choisissez l\'apparence de l\'application.', + 'choose how durations are shown throughout the app. decimal hours are useful for copying time into external systems.' => 'Choisissez comment les durées sont affichées dans l\'application. Les heures décimales sont utiles pour copier le temps vers des systèmes externes.', 'choose whether to show vacation steps' => 'Choisissez si nous devons afficher les étapes de congés.', 'choose your mode' => 'Choisissez votre mode.', 'click "export" at the top of the table and choose "save as csv".' => 'Cliquez sur "Exporter" en haut du tableau et choisissez "Enregistrer en CSV".', + 'clock (7:30)' => 'Horloge (7:30)', 'close' => 'Fermer', 'color' => 'Couleur', 'confirm' => 'Confirmer', @@ -89,6 +91,7 @@ 'day' => 'Jour', 'day equivalent' => 'Équivalent jour', 'days' => 'Jours', + 'decimal hours (7.50)' => 'Heures décimales (7.50)', 'default annual entitlement' => 'Quota annuel par défaut', 'default overview' => 'Vue par défaut', 'default: :value days' => 'Valeur par défaut : :value jours', @@ -341,6 +344,7 @@ 'this shortcut is not supported.' => 'Ce raccourci n’est pas pris en charge.', 'time' => 'Temps', 'time balance' => 'Solde du temps', + 'time display' => 'Affichage du temps', 'time span' => 'Plage horaire', 'timezone' => 'Fuseau horaire', 'to import data from clockify, you must first export it. Simply follow the step-by-step instructions below.' => 'Pour importer des données depuis Clockify, vous devez d\'abord les exporter. Suivez simplement les instructions ci-dessous.', diff --git a/lang/it/app.php b/lang/it/app.php index 89e4feb..39ed390 100644 --- a/lang/it/app.php +++ b/lang/it/app.php @@ -54,9 +54,11 @@ 'chinese' => 'Cinese', 'choose how fractional days are rounded.' => 'Scegli come arrotondare i giorni frazionari.', 'choose the appearance of the application.' => 'Scegli l\'aspetto dell\'applicazione.', + 'choose how durations are shown throughout the app. decimal hours are useful for copying time into external systems.' => 'Scegli come vengono mostrate le durate nell\'app. Le ore decimali sono utili per copiare il tempo in sistemi esterni.', 'choose whether to show vacation steps' => 'Scegli se mostrare i passaggi per le ferie.', 'choose your mode' => 'Scegli la tua modalità.', 'click "export" at the top of the table and choose "save as csv".' => 'Clicca "Esporta" in cima alla tabella e scegli "Salva come CSV".', + 'clock (7:30)' => 'Orario (7:30)', 'close' => 'Chiudi', 'color' => 'Colore', 'confirm' => 'Conferma', @@ -89,6 +91,7 @@ 'day' => 'Giorno', 'day equivalent' => 'Equivalente giorno', 'days' => 'Giorni', + 'decimal hours (7.50)' => 'Ore decimali (7.50)', 'default annual entitlement' => 'Maturazione annuale predefinita', 'default overview' => 'Panoramica predefinita', 'default: :value days' => 'Predefinito: :value giorni', @@ -341,6 +344,7 @@ 'this shortcut is not supported.' => 'Questa scorciatoia non è supportata.', 'time' => 'Tempo', 'time balance' => 'Saldo ore', + 'time display' => 'Formato orario', 'time span' => 'Intervallo di tempo', 'timezone' => 'Fuso orario', 'to import data from clockify, you must first export it. Simply follow the step-by-step instructions below.' => 'Per importare dati da Clockify, devi prima esportarli. Segui semplicemente le istruzioni passo-passo qui sotto.', diff --git a/lang/pt_BR/app.php b/lang/pt_BR/app.php index d907a76..a9879a6 100644 --- a/lang/pt_BR/app.php +++ b/lang/pt_BR/app.php @@ -54,9 +54,11 @@ 'chinese' => 'Chinês', 'choose how fractional days are rounded.' => 'Escolha como os dias fracionados são arredondados.', 'choose the appearance of the application.' => 'Escolha a aparência do aplicativo.', + 'choose how durations are shown throughout the app. decimal hours are useful for copying time into external systems.' => 'Escolha como as durações são exibidas no aplicativo. Horas decimais são úteis para copiar o tempo para sistemas externos.', 'choose whether to show vacation steps' => 'Escolha se deseja mostrar as etapas de férias.', 'choose your mode' => 'Escolha seu modo.', 'click "export" at the top of the table and choose "save as csv".' => 'Clique em "Exportar" no topo da tabela e escolha "Salvar como CSV".', + 'clock (7:30)' => 'Relógio (7:30)', 'close' => 'Fechar', 'color' => 'Cor', 'confirm' => 'Confirmar', @@ -89,6 +91,7 @@ 'day' => 'Dia', 'day equivalent' => 'Equivalente em dias', 'days' => 'Dias', + 'decimal hours (7.50)' => 'Horas decimais (7.50)', 'default annual entitlement' => 'Direito anual padrão', 'default overview' => 'Visão geral padrão', 'default: :value days' => 'Padrão: :value dias', @@ -341,6 +344,7 @@ 'this shortcut is not supported.' => 'Este atalho não é suportado.', 'time' => 'Tempo', 'time balance' => 'Saldo de Horas', + 'time display' => 'Formato de tempo', 'time span' => 'Período de tempo', 'timezone' => 'Fuso Horário', 'to import data from clockify, you must first export it. Simply follow the step-by-step instructions below.' => 'Para importar dados do Clockify, você deve primeiro exportá-los. Basta seguir as instruções passo a passo abaixo.', diff --git a/lang/zh_CN/app.php b/lang/zh_CN/app.php index 59bdb4a..73d55c1 100644 --- a/lang/zh_CN/app.php +++ b/lang/zh_CN/app.php @@ -54,9 +54,11 @@ 'chinese' => '中文', 'choose how fractional days are rounded.' => '控制休假日的小数步长。', 'choose the appearance of the application.' => '选择应用程序的外观。', + 'choose how durations are shown throughout the app. decimal hours are useful for copying time into external systems.' => '选择应用中时长的显示方式。十进制小时便于将时间复制到外部系统中。', 'choose whether to show vacation steps' => '选择是否显示休假步骤。', 'choose your mode' => '选择你的模式。', 'click "export" at the top of the table and choose "save as csv".' => '点击表格上方的“导出”,然后选择“另存为 CSV”。', + 'clock (7:30)' => '时钟格式 (7:30)', 'close' => '关闭', 'color' => '颜色', 'confirm' => '确认', @@ -89,6 +91,7 @@ 'day' => '天', 'day equivalent' => '折算天数', 'days' => '天', + 'decimal hours (7.50)' => '十进制小时 (7.50)', 'default annual entitlement' => '默认年假配额', 'default overview' => '默认概览', 'default: :value days' => '默认值::value 天', @@ -341,6 +344,7 @@ 'this shortcut is not supported.' => '该快捷键不受支持。', 'time' => '时间', 'time balance' => '时间结余', + 'time display' => '时间显示', 'time span' => '时间段', 'timezone' => '时区', 'to import data from clockify, you must first export it. Simply follow the step-by-step instructions below.' => '若要从 Clockify 导入数据,您必须先导出数据。只需遵循以下逐步指示即可。', diff --git a/resources/js/Components/TimestampTypeBadge.vue b/resources/js/Components/TimestampTypeBadge.vue index ebfdcd4..acef53b 100644 --- a/resources/js/Components/TimestampTypeBadge.vue +++ b/resources/js/Components/TimestampTypeBadge.vue @@ -7,7 +7,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger } from '@/Components/ui/dropdown-menu' -import { secToFormat } from '@/lib/utils' +import { secToFormat, secToUnit } from '@/lib/utils' import { GetTimeProjectDetails } from '@/types' import { Link } from '@inertiajs/vue3' import { @@ -85,6 +85,7 @@ const badgeDetails = { const { title: badgeTitle, icon: badgeIcon, color: badgeColor } = badgeDetails[props.type] || badgeDetails.default const durationLabel = computed(() => secToFormat(props.duration ?? 0, true, true, true)) +const durationUnit = computed(() => secToUnit(props.duration ?? 0, true))