From e41b98864f5cd243d4c072419c83c9b6d51bf68b Mon Sep 17 00:00:00 2001 From: Grigory Frolov <2168057+gynsus@users.noreply.github.com> Date: Fri, 21 Aug 2026 22:24:54 +0300 Subject: [PATCH 1/4] feat: workspace timezone setting for calendar and post dates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A nullable IANA timezone on the workspace (Settings → Workspace). When set, every date the app renders or schedules — the calendar, the post lists (all/scheduled/posted/drafts) and the schedule picker — uses it via the shared date module's getUserTimezone(); empty keeps the pre-setting behavior (the viewer's browser timezone). Shared through the auth Inertia props so the whole team sees the same wall-clock times. --- .../App/Workspace/UpdateWorkspaceRequest.php | 1 + .../AuthWorkspaceResource.php | 1 + app/Models/Workspace.php | 1 + ...20000_add_timezone_to_workspaces_table.php | 26 ++++++++++++++++ lang/ar/settings.php | 3 ++ lang/de/settings.php | 3 ++ lang/el/settings.php | 3 ++ lang/en/settings.php | 3 ++ lang/es/settings.php | 3 ++ lang/fr/settings.php | 3 ++ lang/it/settings.php | 3 ++ lang/ja/settings.php | 3 ++ lang/ko/settings.php | 3 ++ lang/nl/settings.php | 3 ++ lang/pl/settings.php | 3 ++ lang/pt-BR/settings.php | 3 ++ lang/ru/settings.php | 3 ++ lang/tr/settings.php | 3 ++ lang/uk/settings.php | 3 ++ lang/zh/settings.php | 3 ++ .../js/components/settings/WorkspaceTab.vue | 25 ++++++++++++++++ resources/js/date.ts | 8 +++++ .../js/pages/settings/workspace/Workspace.vue | 1 + tests/Feature/WorkspaceControllerTest.php | 30 +++++++++++++++++++ 24 files changed, 141 insertions(+) create mode 100644 database/migrations/2026_08_20_120000_add_timezone_to_workspaces_table.php diff --git a/app/Http/Requests/App/Workspace/UpdateWorkspaceRequest.php b/app/Http/Requests/App/Workspace/UpdateWorkspaceRequest.php index ad8add4ee..b8e17e970 100644 --- a/app/Http/Requests/App/Workspace/UpdateWorkspaceRequest.php +++ b/app/Http/Requests/App/Workspace/UpdateWorkspaceRequest.php @@ -35,6 +35,7 @@ public function rules(): array 'image_style' => ['sometimes', 'required', 'string', Rule::in(ImageStyle::values())], 'content_language' => ['sometimes', 'string', Rule::in(ContentLanguage::values())], 'logo_url' => ['nullable', 'url', 'max:1024'], + 'timezone' => ['sometimes', 'nullable', 'string', 'timezone:all'], ]; } } diff --git a/app/Http/Resources/App/HandleInertiaRequests/AuthWorkspaceResource.php b/app/Http/Resources/App/HandleInertiaRequests/AuthWorkspaceResource.php index 4413b15ac..d51da01f4 100644 --- a/app/Http/Resources/App/HandleInertiaRequests/AuthWorkspaceResource.php +++ b/app/Http/Resources/App/HandleInertiaRequests/AuthWorkspaceResource.php @@ -18,6 +18,7 @@ public static function make(Workspace $workspace, ?User $user = null): array 'id' => $workspace->id, 'name' => $workspace->name, 'logo_url' => $workspace->logo_url, + 'timezone' => $workspace->timezone, 'created_at' => $workspace->created_at->toIso8601String(), 'role' => $user ? self::resolveRole($workspace, $user) : null, ]; diff --git a/app/Models/Workspace.php b/app/Models/Workspace.php index aba4944ad..06f4f5d1f 100644 --- a/app/Models/Workspace.php +++ b/app/Models/Workspace.php @@ -33,6 +33,7 @@ class Workspace extends Model 'brand_font', 'image_style', 'content_language', + 'timezone', ]; protected function casts(): array diff --git a/database/migrations/2026_08_20_120000_add_timezone_to_workspaces_table.php b/database/migrations/2026_08_20_120000_add_timezone_to_workspaces_table.php new file mode 100644 index 000000000..f15b8e9ae --- /dev/null +++ b/database/migrations/2026_08_20_120000_add_timezone_to_workspaces_table.php @@ -0,0 +1,26 @@ +string('timezone', 64)->nullable()->after('content_language'); + }); + } + + public function down(): void + { + Schema::table('workspaces', function (Blueprint $table) { + $table->dropColumn('timezone'); + }); + } +}; diff --git a/lang/ar/settings.php b/lang/ar/settings.php index d3dc72c88..044ea3fb2 100644 --- a/lang/ar/settings.php +++ b/lang/ar/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'الصورة الرمزية', 'name' => 'الاسم', 'name_placeholder' => 'الاسم الكامل', + 'timezone' => 'المنطقة الزمنية', + 'timezone_auto' => 'تلقائي — منطقة المتصفح (:tz)', + 'timezone_description' => 'يُعرض التقويم والجدولة وتواريخ المنشورات بهذه المنطقة الزمنية لجميع أعضاء مساحة العمل.', 'email' => 'البريد الإلكتروني', 'email_placeholder' => 'البريد الإلكتروني', 'email_unverified' => 'لم يتم تأكيد بريدك الإلكتروني.', diff --git a/lang/de/settings.php b/lang/de/settings.php index e1bfc9991..768ac1c26 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -54,6 +54,9 @@ 'avatar' => 'Avatar', 'name' => 'Name', 'name_placeholder' => 'Vollständiger Name', + 'timezone' => 'Zeitzone', + 'timezone_auto' => 'Auto — Browser-Zeitzone (:tz)', + 'timezone_description' => 'Kalender, Zeitplan und Beitragsdaten werden für alle im Workspace in dieser Zeitzone angezeigt.', 'email' => 'E-Mail-Adresse', 'email_placeholder' => 'E-Mail-Adresse', 'email_unverified' => 'Deine E-Mail-Adresse ist nicht bestätigt.', diff --git a/lang/el/settings.php b/lang/el/settings.php index 3ce5941f8..74d1fb5ed 100644 --- a/lang/el/settings.php +++ b/lang/el/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'Άβαταρ', 'name' => 'Όνομα', 'name_placeholder' => 'Ονοματεπώνυμο', + 'timezone' => 'Ζώνη ώρας', + 'timezone_auto' => 'Αυτόματα — ζώνη του προγράμματος περιήγησης (:tz)', + 'timezone_description' => 'Το ημερολόγιο, ο προγραμματισμός και οι ημερομηνίες αναρτήσεων εμφανίζονται σε αυτήν τη ζώνη για όλο το workspace.', 'email' => 'Διεύθυνση email', 'email_placeholder' => 'Διεύθυνση email', 'email_unverified' => 'Η διεύθυνση email σας δεν έχει επαληθευτεί.', diff --git a/lang/en/settings.php b/lang/en/settings.php index b4b7dc806..7b3e1831c 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'Avatar', 'name' => 'Name', 'name_placeholder' => 'Full name', + 'timezone' => 'Timezone', + 'timezone_auto' => 'Auto — browser timezone (:tz)', + 'timezone_description' => 'Calendar, schedule and post dates are shown in this timezone for everyone in the workspace.', 'email' => 'Email address', 'email_placeholder' => 'Email address', 'email_unverified' => 'Your email address is unverified.', diff --git a/lang/es/settings.php b/lang/es/settings.php index 44310f2b3..aea1870ce 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'Avatar', 'name' => 'Nombre', 'name_placeholder' => 'Nombre completo', + 'timezone' => 'Zona horaria', + 'timezone_auto' => 'Auto — zona del navegador (:tz)', + 'timezone_description' => 'El calendario, la programación y las fechas de publicaciones se muestran en esta zona para todo el workspace.', 'email' => 'Correo electrónico', 'email_placeholder' => 'Correo electrónico', 'email_unverified' => 'Tu correo electrónico no ha sido verificado.', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index 2cede7c15..44d71d3db 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'Avatar', 'name' => 'Nom', 'name_placeholder' => 'Nom complet', + 'timezone' => 'Fuseau horaire', + 'timezone_auto' => 'Auto — fuseau du navigateur (:tz)', + 'timezone_description' => 'Le calendrier, la planification et les dates des publications s’affichent dans ce fuseau pour tout le workspace.', 'email' => 'Adresse e-mail', 'email_placeholder' => 'Adresse e-mail', 'email_unverified' => 'Votre adresse e-mail n\'est pas vérifiée.', diff --git a/lang/it/settings.php b/lang/it/settings.php index 08f8e3417..2693108f0 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'Avatar', 'name' => 'Nome', 'name_placeholder' => 'Nome completo', + 'timezone' => 'Fuso orario', + 'timezone_auto' => 'Auto — fuso del browser (:tz)', + 'timezone_description' => 'Calendario, programmazione e date dei post vengono mostrati in questo fuso per tutto il workspace.', 'email' => 'Indirizzo email', 'email_placeholder' => 'Indirizzo email', 'email_unverified' => 'Il tuo indirizzo email non è verificato.', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index 94e76beb7..31701a4f6 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'アバター', 'name' => '名前', 'name_placeholder' => '氏名', + 'timezone' => 'タイムゾーン', + 'timezone_auto' => '自動 — ブラウザのタイムゾーン (:tz)', + 'timezone_description' => 'カレンダー、スケジュール、投稿日時はワークスペース全員にこのタイムゾーンで表示されます。', 'email' => 'メールアドレス', 'email_placeholder' => 'メールアドレス', 'email_unverified' => 'メールアドレスが未確認です。', diff --git a/lang/ko/settings.php b/lang/ko/settings.php index 77fb6569b..feabb001f 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -52,6 +52,9 @@ 'avatar' => '아바타', 'name' => '이름', 'name_placeholder' => '전체 이름', + 'timezone' => '시간대', + 'timezone_auto' => '자동 — 브라우저 시간대 (:tz)', + 'timezone_description' => '캘린더, 일정, 게시물 날짜가 워크스페이스 전체에 이 시간대로 표시됩니다.', 'email' => '이메일 주소', 'email_placeholder' => '이메일 주소', 'email_unverified' => '이메일 주소가 인증되지 않았습니다.', diff --git a/lang/nl/settings.php b/lang/nl/settings.php index 46c1eb79d..67f3aaf4d 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'Avatar', 'name' => 'Naam', 'name_placeholder' => 'Volledige naam', + 'timezone' => 'Tijdzone', + 'timezone_auto' => 'Auto — browsertijdzone (:tz)', + 'timezone_description' => 'Kalender, planning en berichtdatums worden voor iedereen in de workspace in deze tijdzone getoond.', 'email' => 'E-mailadres', 'email_placeholder' => 'E-mailadres', 'email_unverified' => 'Je e-mailadres is niet geverifieerd.', diff --git a/lang/pl/settings.php b/lang/pl/settings.php index 89520348f..e460940ff 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'Awatar', 'name' => 'Imię i nazwisko', 'name_placeholder' => 'Imię i nazwisko', + 'timezone' => 'Strefa czasowa', + 'timezone_auto' => 'Auto — strefa przeglądarki (:tz)', + 'timezone_description' => 'Kalendarz, harmonogram i daty postów są pokazywane w tej strefie wszystkim w workspace.', 'email' => 'Adres e-mail', 'email_placeholder' => 'Adres e-mail', 'email_unverified' => 'Twój adres e-mail nie został zweryfikowany.', diff --git a/lang/pt-BR/settings.php b/lang/pt-BR/settings.php index 1ec2a7936..a40ca25b8 100644 --- a/lang/pt-BR/settings.php +++ b/lang/pt-BR/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'Avatar', 'name' => 'Nome', 'name_placeholder' => 'Nome completo', + 'timezone' => 'Fuso horário', + 'timezone_auto' => 'Auto — fuso do navegador (:tz)', + 'timezone_description' => 'Calendário, agendamento e datas das publicações são exibidos neste fuso para todo o workspace.', 'email' => 'Endereço de e-mail', 'email_placeholder' => 'Endereço de e-mail', 'email_unverified' => 'Seu endereço de e-mail não foi verificado.', diff --git a/lang/ru/settings.php b/lang/ru/settings.php index 79cf9d15f..3ab2a1d9b 100644 --- a/lang/ru/settings.php +++ b/lang/ru/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'Аватар', 'name' => 'Имя', 'name_placeholder' => 'Полное имя', + 'timezone' => 'Часовой пояс', + 'timezone_auto' => 'Авто — часовой пояс браузера (:tz)', + 'timezone_description' => 'Календарь, расписание и даты постов показываются в этом поясе всем участникам воркспейса.', 'email' => 'Адрес email', 'email_placeholder' => 'Адрес email', 'email_unverified' => 'Ваш адрес email не подтверждён.', diff --git a/lang/tr/settings.php b/lang/tr/settings.php index f2852da05..f7d099ae5 100644 --- a/lang/tr/settings.php +++ b/lang/tr/settings.php @@ -54,6 +54,9 @@ 'avatar' => 'Avatar', 'name' => 'Ad', 'name_placeholder' => 'Ad soyad', + 'timezone' => 'Saat dilimi', + 'timezone_auto' => 'Otomatik — tarayıcı saat dilimi (:tz)', + 'timezone_description' => 'Takvim, plan ve gönderi tarihleri çalışma alanındaki herkese bu dilimde gösterilir.', 'email' => 'E-posta adresi', 'email_placeholder' => 'E-posta adresi', 'email_unverified' => 'E-posta adresiniz doğrulanmadı.', diff --git a/lang/uk/settings.php b/lang/uk/settings.php index b53ab183b..22b181e0b 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -52,6 +52,9 @@ 'avatar' => 'Аватар', 'name' => 'Ім’я', 'name_placeholder' => 'Повне ім’я', + 'timezone' => 'Часовий пояс', + 'timezone_auto' => 'Авто — часовий пояс браузера (:tz)', + 'timezone_description' => 'Календар, розклад і дати постів показуються в цьому поясі всім учасникам воркспейсу.', 'email' => 'Адреса email', 'email_placeholder' => 'Адреса email', 'email_unverified' => 'Вашу адресу email не підтверджено.', diff --git a/lang/zh/settings.php b/lang/zh/settings.php index e830106c5..6ca50e4f9 100644 --- a/lang/zh/settings.php +++ b/lang/zh/settings.php @@ -52,6 +52,9 @@ 'avatar' => '头像', 'name' => '姓名', 'name_placeholder' => '全名', + 'timezone' => '时区', + 'timezone_auto' => '自动 — 浏览器时区 (:tz)', + 'timezone_description' => '日历、排期和帖子日期将以此时区向工作区所有成员显示。', 'email' => '邮箱地址', 'email_placeholder' => '邮箱地址', 'email_unverified' => '你的邮箱地址尚未验证。', diff --git a/resources/js/components/settings/WorkspaceTab.vue b/resources/js/components/settings/WorkspaceTab.vue index 100fdc5c1..ba09d4fa0 100644 --- a/resources/js/components/settings/WorkspaceTab.vue +++ b/resources/js/components/settings/WorkspaceTab.vue @@ -18,6 +18,7 @@ interface Workspace { name: string; has_logo: boolean; logo_url: string | null; + timezone?: string | null; } defineProps<{ @@ -27,6 +28,16 @@ defineProps<{ }>(); const { canManageBilling } = useWorkspaceRole(); + +const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + +const timezones: string[] = (() => { + try { + return (Intl as any).supportedValuesOf('timeZone') as string[]; + } catch { + return [browserTimezone]; + } +})();