Explorar el Código

Respect week start day everywhere and fix WebSocket patch in Docker builds (#208)

* make week start from user setting

* ws

* saniteringen
Bernt Christian Egeland hace 1 mes
padre
commit
9874a20218

+ 1 - 1
package.json

@@ -12,7 +12,7 @@
     "format": "biome format . --write",
     "format": "biome format . --write",
     "check": "biome check .",
     "check": "biome check .",
     "check:fix": "biome check . --write",
     "check:fix": "biome check . --write",
-    "prepare": "next-ws patch",
+    "prepare": "next-ws patch --yes",
     "test": "vitest run",
     "test": "vitest run",
     "test:coverage": "vitest --coverage"
     "test:coverage": "vitest --coverage"
   },
   },

+ 1 - 0
src/app/(authenticated)/layout.tsx

@@ -120,6 +120,7 @@ export default async function DashboardLayout({
         dateFormat={data.dateFormat}
         dateFormat={data.dateFormat}
         timeFormat={data.timeFormat}
         timeFormat={data.timeFormat}
         timezone={data.timezone}
         timezone={data.timezone}
+        weekStartDay={data.weekStartDay}
       >
       >
       <CurrencySettingsProvider
       <CurrencySettingsProvider
         currencyCode={data.currencyCode}
         currencyCode={data.currencyCode}

+ 20 - 0
src/app/(authenticated)/settings/localization/localization-settings.tsx

@@ -187,6 +187,9 @@ export function LocalizationSettings({ settings }: { settings: Record<string, st
   )
   )
   const [timeFormat, setTimeFormat] = useState(settings[SETTING_KEYS.TIME_FORMAT] || '12h')
   const [timeFormat, setTimeFormat] = useState(settings[SETTING_KEYS.TIME_FORMAT] || '12h')
   const [timezone, setTimezone] = useState(settings[SETTING_KEYS.TIMEZONE] || '')
   const [timezone, setTimezone] = useState(settings[SETTING_KEYS.TIMEZONE] || '')
+  const [weekStartDay, setWeekStartDay] = useState(
+    settings[SETTING_KEYS.WORKBOARD_WEEK_START_DAY] || '1'
+  )
 
 
   const [forceCustomerLocale, setForceCustomerLocale] = useState(
   const [forceCustomerLocale, setForceCustomerLocale] = useState(
     settings[SETTING_KEYS.FORCE_CUSTOMER_LOCALE] === 'true'
     settings[SETTING_KEYS.FORCE_CUSTOMER_LOCALE] === 'true'
@@ -235,6 +238,7 @@ export function LocalizationSettings({ settings }: { settings: Record<string, st
       [SETTING_KEYS.DATE_FORMAT]: dateFormat,
       [SETTING_KEYS.DATE_FORMAT]: dateFormat,
       [SETTING_KEYS.TIME_FORMAT]: timeFormat,
       [SETTING_KEYS.TIME_FORMAT]: timeFormat,
       [SETTING_KEYS.TIMEZONE]: timezone,
       [SETTING_KEYS.TIMEZONE]: timezone,
+      [SETTING_KEYS.WORKBOARD_WEEK_START_DAY]: weekStartDay,
       [SETTING_KEYS.WORKSHOP_DEFAULT_COUNTRY_CODE]: normalizedCountryCode ?? '',
       [SETTING_KEYS.WORKSHOP_DEFAULT_COUNTRY_CODE]: normalizedCountryCode ?? '',
     })
     })
     // Reflect the normalized form back in the input.
     // Reflect the normalized form back in the input.
@@ -470,6 +474,22 @@ export function LocalizationSettings({ settings }: { settings: Record<string, st
                 </Select>
                 </Select>
               </div>
               </div>
 
 
+              <div className="space-y-2">
+                <Label>{t('workshop.weekStartDay')}</Label>
+                <Select value={weekStartDay} onValueChange={setWeekStartDay}>
+                  <SelectTrigger className="w-48">
+                    <SelectValue />
+                  </SelectTrigger>
+                  <SelectContent>
+                    {[0, 1, 2, 3, 4, 5, 6].map((d) => (
+                      <SelectItem key={d} value={String(d)}>
+                        {t(`workshop.weekDays.${d}`)}
+                      </SelectItem>
+                    ))}
+                  </SelectContent>
+                </Select>
+              </div>
+
               <div className="space-y-2">
               <div className="space-y-2">
                 <Label>{t('appearance.timezone')}</Label>
                 <Label>{t('appearance.timezone')}</Label>
                 <Popover open={timezoneOpen} onOpenChange={setTimezoneOpen}>
                 <Popover open={timezoneOpen} onOpenChange={setTimezoneOpen}>

+ 1 - 20
src/app/(authenticated)/settings/workshop/workshop-settings.tsx

@@ -76,9 +76,6 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
   const [unitSystem, setUnitSystem] = useState(
   const [unitSystem, setUnitSystem] = useState(
     settings[SETTING_KEYS.UNIT_SYSTEM] || "imperial"
     settings[SETTING_KEYS.UNIT_SYSTEM] || "imperial"
   );
   );
-  const [weekStartDay, setWeekStartDay] = useState(
-    settings[SETTING_KEYS.WORKBOARD_WEEK_START_DAY] || "1"
-  );
   const [workDayStart, setWorkDayStart] = useState(
   const [workDayStart, setWorkDayStart] = useState(
     settings[SETTING_KEYS.WORKBOARD_WORK_DAY_START] || "07:00"
     settings[SETTING_KEYS.WORKBOARD_WORK_DAY_START] || "07:00"
   );
   );
@@ -139,7 +136,6 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
       [SETTING_KEYS.DEFAULT_TECHNICIAN]: selectedTechName,
       [SETTING_KEYS.DEFAULT_TECHNICIAN]: selectedTechName,
       [SETTING_KEYS.DEFAULT_LABOR_RATE]: defaultLaborRate,
       [SETTING_KEYS.DEFAULT_LABOR_RATE]: defaultLaborRate,
       [SETTING_KEYS.UNIT_SYSTEM]: unitSystem,
       [SETTING_KEYS.UNIT_SYSTEM]: unitSystem,
-      [SETTING_KEYS.WORKBOARD_WEEK_START_DAY]: weekStartDay,
       [SETTING_KEYS.WORKBOARD_WORK_DAY_START]: workDayStart,
       [SETTING_KEYS.WORKBOARD_WORK_DAY_START]: workDayStart,
       [SETTING_KEYS.WORKBOARD_WORK_DAY_END]: workDayEnd,
       [SETTING_KEYS.WORKBOARD_WORK_DAY_END]: workDayEnd,
     });
     });
@@ -292,22 +288,7 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
             </div>
             </div>
           </div>
           </div>
 
 
-          <div className="grid gap-4 sm:grid-cols-3">
-            <div className="space-y-2">
-              <Label htmlFor="weekStartDay">{t('workshop.weekStartDay')}</Label>
-              <Select value={weekStartDay} onValueChange={setWeekStartDay}>
-                <SelectTrigger>
-                  <SelectValue />
-                </SelectTrigger>
-                <SelectContent>
-                  {[0, 1, 2, 3, 4, 5, 6].map((d) => (
-                    <SelectItem key={d} value={String(d)}>
-                      {t(`workshop.weekDays.${d}`)}
-                    </SelectItem>
-                  ))}
-                </SelectContent>
-              </Select>
-            </div>
+          <div className="grid gap-4 sm:grid-cols-2">
             <div className="space-y-2">
             <div className="space-y-2">
               <Label htmlFor="workDayStart">{t('workshop.workDayStart')}</Label>
               <Label htmlFor="workDayStart">{t('workshop.workDayStart')}</Label>
               <Input
               <Input

+ 12 - 10
src/app/(presenter)/work-board/presenter/page.tsx

@@ -6,11 +6,10 @@ import { WorkBoardPresenter } from "@/features/workboard/Components/WorkBoardPre
 
 
 export const dynamic = "force-dynamic";
 export const dynamic = "force-dynamic";
 
 
-function getMonday(date: Date): string {
+function getWeekStart(date: Date, weekStartDay: number): string {
   const d = new Date(date);
   const d = new Date(date);
-  const day = d.getDay();
-  const diff = d.getDate() - day + (day === 0 ? -6 : 1);
-  d.setDate(diff);
+  const diff = (d.getDay() - weekStartDay + 7) % 7;
+  d.setDate(d.getDate() - diff);
   const y = d.getFullYear();
   const y = d.getFullYear();
   const m = String(d.getMonth() + 1).padStart(2, "0");
   const m = String(d.getMonth() + 1).padStart(2, "0");
   const dd = String(d.getDate()).padStart(2, "0");
   const dd = String(d.getDate()).padStart(2, "0");
@@ -24,18 +23,20 @@ export default async function WorkBoardPresenterPage({
 }) {
 }) {
   const params = await searchParams;
   const params = await searchParams;
   const baseDate = params.date ? new Date(params.date + "T12:00:00") : new Date();
   const baseDate = params.date ? new Date(params.date + "T12:00:00") : new Date();
-  const weekStart = getMonday(baseDate);
 
 
-  const [techResult, assignResult, settingsResult] = await Promise.all([
-    getTechnicians(),
-    getBoardJobs(weekStart),
-    getWorkBoardSettings(),
-  ]);
+  const techPromise = getTechnicians();
+  const settingsResult = await getWorkBoardSettings();
 
 
   const boardSettings = settingsResult.success && settingsResult.data
   const boardSettings = settingsResult.success && settingsResult.data
     ? settingsResult.data
     ? settingsResult.data
     : { weekStartDay: 1, workDayStart: "07:00", workDayEnd: "15:00" };
     : { weekStartDay: 1, workDayStart: "07:00", workDayEnd: "15:00" };
 
 
+  const weekStart = getWeekStart(baseDate, boardSettings.weekStartDay);
+  const [techResult, assignResult] = await Promise.all([
+    techPromise,
+    getBoardJobs(weekStart),
+  ]);
+
   if (!techResult.success) {
   if (!techResult.success) {
     return (
     return (
       <div className="flex h-screen items-center justify-center">
       <div className="flex h-screen items-center justify-center">
@@ -62,6 +63,7 @@ export default async function WorkBoardPresenterPage({
         initialWeekStart={weekStart}
         initialWeekStart={weekStart}
         workDayStart={boardSettings.workDayStart}
         workDayStart={boardSettings.workDayStart}
         workDayEnd={boardSettings.workDayEnd}
         workDayEnd={boardSettings.workDayEnd}
+        weekStartDay={boardSettings.weekStartDay}
       />
       />
     </Suspense>
     </Suspense>
   );
   );

+ 8 - 0
src/components/date-settings-context.tsx

@@ -7,12 +7,14 @@ interface DateSettings {
   dateFormat: string;
   dateFormat: string;
   timeFormat: "12h" | "24h";
   timeFormat: "12h" | "24h";
   timezone: string;
   timezone: string;
+  weekStartDay: number;
 }
 }
 
 
 const defaultSettings: DateSettings = {
 const defaultSettings: DateSettings = {
   dateFormat: DEFAULT_DATE_FORMAT,
   dateFormat: DEFAULT_DATE_FORMAT,
   timeFormat: DEFAULT_TIME_FORMAT,
   timeFormat: DEFAULT_TIME_FORMAT,
   timezone: "",
   timezone: "",
+  weekStartDay: 1,
 };
 };
 
 
 const DateSettingsContext = createContext<DateSettings>(defaultSettings);
 const DateSettingsContext = createContext<DateSettings>(defaultSettings);
@@ -21,17 +23,23 @@ export function DateSettingsProvider({
   dateFormat,
   dateFormat,
   timeFormat,
   timeFormat,
   timezone,
   timezone,
+  weekStartDay,
   children,
   children,
 }: {
 }: {
   dateFormat?: string;
   dateFormat?: string;
   timeFormat?: string;
   timeFormat?: string;
   timezone?: string;
   timezone?: string;
+  weekStartDay?: number;
   children: React.ReactNode;
   children: React.ReactNode;
 }) {
 }) {
   const value: DateSettings = {
   const value: DateSettings = {
     dateFormat: dateFormat || DEFAULT_DATE_FORMAT,
     dateFormat: dateFormat || DEFAULT_DATE_FORMAT,
     timeFormat: (timeFormat === "24h" ? "24h" : "12h"),
     timeFormat: (timeFormat === "24h" ? "24h" : "12h"),
     timezone: timezone || "",
     timezone: timezone || "",
+    weekStartDay:
+      weekStartDay != null && Number.isInteger(weekStartDay) && weekStartDay >= 0 && weekStartDay <= 6
+        ? weekStartDay
+        : 1,
   };
   };
 
 
   return (
   return (

+ 3 - 0
src/components/ui/calendar.tsx

@@ -14,6 +14,7 @@ import {
 
 
 import { cn } from "@/lib/utils"
 import { cn } from "@/lib/utils"
 import { Button, buttonVariants } from "@/components/ui/button"
 import { Button, buttonVariants } from "@/components/ui/button"
+import { useDateSettings } from "@/components/date-settings-context"
 
 
 function Calendar({
 function Calendar({
   className,
   className,
@@ -28,6 +29,7 @@ function Calendar({
   buttonVariant?: React.ComponentProps<typeof Button>["variant"]
   buttonVariant?: React.ComponentProps<typeof Button>["variant"]
 }) {
 }) {
   const defaultClassNames = getDefaultClassNames()
   const defaultClassNames = getDefaultClassNames()
+  const { weekStartDay } = useDateSettings()
 
 
   return (
   return (
     <DayPicker
     <DayPicker
@@ -175,6 +177,7 @@ function Calendar({
         ...components,
         ...components,
       }}
       }}
       {...props}
       {...props}
+      weekStartsOn={props.weekStartsOn ?? ((weekStartDay % 7) as 0 | 1 | 2 | 3 | 4 | 5 | 6)}
     />
     />
   )
   )
 }
 }

+ 3 - 0
src/components/ui/datetime-picker.tsx

@@ -22,6 +22,7 @@ import {
   SelectValue,
   SelectValue,
 } from "@/components/ui/select";
 } from "@/components/ui/select";
 import { DayPicker, type DayPickerProps } from "react-day-picker";
 import { DayPicker, type DayPickerProps } from "react-day-picker";
+import { useDateSettings } from "@/components/date-settings-context";
 
 
 // ---------- UTILITY FUNCTIONS ----------
 // ---------- UTILITY FUNCTIONS ----------
 
 
@@ -229,6 +230,7 @@ function Calendar({
   yearRange = 50,
   yearRange = 50,
   ...props
   ...props
 }: DayPickerProps & { yearRange?: number }) {
 }: DayPickerProps & { yearRange?: number }) {
+  const { weekStartDay } = useDateSettings();
   const MONTHS = React.useMemo(() => {
   const MONTHS = React.useMemo(() => {
     let locale: Pick<Locale, "options" | "localize" | "formatLong"> = enUS;
     let locale: Pick<Locale, "options" | "localize" | "formatLong"> = enUS;
     const { options, localize, formatLong } = props.locale || {};
     const { options, localize, formatLong } = props.locale || {};
@@ -367,6 +369,7 @@ function Calendar({
         },
         },
       }}
       }}
       {...props}
       {...props}
+      weekStartsOn={props.weekStartsOn ?? ((weekStartDay % 7) as 0 | 1 | 2 | 3 | 4 | 5 | 6)}
     />
     />
   );
   );
 }
 }

+ 7 - 4
src/features/calendar/Components/CalendarClient.tsx

@@ -2,6 +2,7 @@
 
 
 import { useState, useCallback, useMemo } from "react";
 import { useState, useCallback, useMemo } from "react";
 import { useTranslations } from "next-intl";
 import { useTranslations } from "next-intl";
+import { useDateSettings } from "@/components/date-settings-context";
 import { Button } from "@/components/ui/button";
 import { Button } from "@/components/ui/button";
 import { Card, CardContent } from "@/components/ui/card";
 import { Card, CardContent } from "@/components/ui/card";
 import { Checkbox } from "@/components/ui/checkbox";
 import { Checkbox } from "@/components/ui/checkbox";
@@ -54,10 +55,10 @@ interface CalendarClientProps {
   currencyCode: string;
   currencyCode: string;
 }
 }
 
 
-function getMonthDays(year: number, month: number) {
+function getMonthDays(year: number, month: number, weekStartDay: number) {
   const firstDay = new Date(year, month, 1);
   const firstDay = new Date(year, month, 1);
   const lastDay = new Date(year, month + 1, 0);
   const lastDay = new Date(year, month + 1, 0);
-  const startPad = firstDay.getDay();
+  const startPad = (firstDay.getDay() - weekStartDay + 7) % 7;
 
 
   const days: Date[] = [];
   const days: Date[] = [];
 
 
@@ -93,7 +94,9 @@ export default function CalendarClient({
   currencyCode,
   currencyCode,
 }: CalendarClientProps) {
 }: CalendarClientProps) {
   const t = useTranslations('calendar');
   const t = useTranslations('calendar');
-  const WEEKDAYS = [t('weekdays.sun'), t('weekdays.mon'), t('weekdays.tue'), t('weekdays.wed'), t('weekdays.thu'), t('weekdays.fri'), t('weekdays.sat')];
+  const { weekStartDay } = useDateSettings();
+  const WEEKDAY_LABELS = [t('weekdays.sun'), t('weekdays.mon'), t('weekdays.tue'), t('weekdays.wed'), t('weekdays.thu'), t('weekdays.fri'), t('weekdays.sat')];
+  const WEEKDAYS = Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(weekStartDay + i) % 7]);
   const MONTH_NAMES = [t('months.january'), t('months.february'), t('months.march'), t('months.april'), t('months.may'), t('months.june'), t('months.july'), t('months.august'), t('months.september'), t('months.october'), t('months.november'), t('months.december')];
   const MONTH_NAMES = [t('months.january'), t('months.february'), t('months.march'), t('months.april'), t('months.may'), t('months.june'), t('months.july'), t('months.august'), t('months.september'), t('months.october'), t('months.november'), t('months.december')];
 
 
   const [month, setMonth] = useState(initialMonth);
   const [month, setMonth] = useState(initialMonth);
@@ -155,7 +158,7 @@ export default function CalendarClient({
     return { serviceCount, overdueReminders, pendingQuotes, totalRevenue };
     return { serviceCount, overdueReminders, pendingQuotes, totalRevenue };
   }, [events]);
   }, [events]);
 
 
-  const days = getMonthDays(year, month);
+  const days = getMonthDays(year, month, weekStartDay);
 
 
   const fetchEvents = useCallback(async (y: number, m: number) => {
   const fetchEvents = useCallback(async (y: number, m: number) => {
     setLoading(true);
     setLoading(true);

+ 4 - 1
src/features/workboard/Actions/boardActions/assignments.ts

@@ -418,8 +418,11 @@ export async function getWorkBoardSettings() {
         map[s.key] = s.value;
         map[s.key] = s.value;
       }
       }
 
 
+      // Clamp so a corrupt stored value can never produce NaN week starts downstream
+      const parsedWeekStart = parseInt(map["workboard.weekStartDay"] || "1", 10);
+
       return {
       return {
-        weekStartDay: parseInt(map["workboard.weekStartDay"] || "1", 10),
+        weekStartDay: Number.isInteger(parsedWeekStart) && parsedWeekStart >= 0 && parsedWeekStart <= 6 ? parsedWeekStart : 1,
         workDayStart: map["workboard.workDayStart"] || "07:00",
         workDayStart: map["workboard.workDayStart"] || "07:00",
         workDayEnd: map["workboard.workDayEnd"] || "15:00",
         workDayEnd: map["workboard.workDayEnd"] || "15:00",
       } as WorkBoardSettings;
       } as WorkBoardSettings;

+ 11 - 11
src/features/workboard/Components/WorkBoardPresenter.tsx

@@ -21,8 +21,8 @@ function toLocalDateString(d: Date): string {
   return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
   return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
 }
 }
 
 
-function getMonday(date: Date): string {
-  const d = new Date(date); const day = d.getDay(); d.setDate(d.getDate() - day + (day === 0 ? -6 : 1));
+function getWeekStart(date: Date, weekStartDay: number): string {
+  const d = new Date(date); const diff = (d.getDay() - weekStartDay + 7) % 7; d.setDate(d.getDate() - diff);
   return toLocalDateString(d)
   return toLocalDateString(d)
 }
 }
 
 
@@ -32,7 +32,7 @@ function getWeekDays(weekStart: string): string[] {
   return days
   return days
 }
 }
 
 
-const DAY_KEYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] as const
+const DAY_KEYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'] as const
 
 
 function formatWeekRange(weekStart: string, locale?: string): string {
 function formatWeekRange(weekStart: string, locale?: string): string {
   const start = new Date(weekStart + 'T12:00:00'); const end = new Date(start); end.setDate(end.getDate() + 6);
   const start = new Date(weekStart + 'T12:00:00'); const end = new Date(start); end.setDate(end.getDate() + 6);
@@ -65,8 +65,8 @@ function PresenterJobCard({ job }: { job: WorkBoardJob }) {
   )
   )
 }
 }
 
 
-export function WorkBoardPresenter({ initialTechnicians, initialAssignments, initialWeekStart, workDayStart = '07:00', workDayEnd = '15:00' }: {
-  initialTechnicians: Technician[]; initialAssignments: WorkBoardJob[]; initialWeekStart: string; workDayStart?: string; workDayEnd?: string
+export function WorkBoardPresenter({ initialTechnicians, initialAssignments, initialWeekStart, workDayStart = '07:00', workDayEnd = '15:00', weekStartDay = 1 }: {
+  initialTechnicians: Technician[]; initialAssignments: WorkBoardJob[]; initialWeekStart: string; workDayStart?: string; workDayEnd?: string; weekStartDay?: number
 }) {
 }) {
   const store = useWorkBoardStore()
   const store = useWorkBoardStore()
   const t = useTranslations('workBoard.presenter')
   const t = useTranslations('workBoard.presenter')
@@ -96,17 +96,17 @@ export function WorkBoardPresenter({ initialTechnicians, initialAssignments, ini
     if (techRes.success && techRes.data) store.setTechnicians(techRes.data as Technician[])
     if (techRes.success && techRes.data) store.setTechnicians(techRes.data as Technician[])
   }, [store])
   }, [store])
 
 
-  const ensureWeekLoaded = useCallback((dateStr: string) => { const m = getMonday(new Date(dateStr + 'T12:00:00')); if (m !== weekStart) loadWeekData(m) }, [weekStart, loadWeekData])
+  const ensureWeekLoaded = useCallback((dateStr: string) => { const m = getWeekStart(new Date(dateStr + 'T12:00:00'), weekStartDay); if (m !== weekStart) loadWeekData(m) }, [weekStart, loadWeekData, weekStartDay])
   const handlePrev = () => { if (viewMode === 'week') { const d = new Date(weekStart + 'T12:00:00'); d.setDate(d.getDate() - 7); loadWeekData(toLocalDateString(d)) } else { const d = new Date(selectedDate + 'T12:00:00'); d.setDate(d.getDate() - 1); const nd = toLocalDateString(d); setSelectedDate(nd); ensureWeekLoaded(nd) } }
   const handlePrev = () => { if (viewMode === 'week') { const d = new Date(weekStart + 'T12:00:00'); d.setDate(d.getDate() - 7); loadWeekData(toLocalDateString(d)) } else { const d = new Date(selectedDate + 'T12:00:00'); d.setDate(d.getDate() - 1); const nd = toLocalDateString(d); setSelectedDate(nd); ensureWeekLoaded(nd) } }
   const handleNext = () => { if (viewMode === 'week') { const d = new Date(weekStart + 'T12:00:00'); d.setDate(d.getDate() + 7); loadWeekData(toLocalDateString(d)) } else { const d = new Date(selectedDate + 'T12:00:00'); d.setDate(d.getDate() + 1); const nd = toLocalDateString(d); setSelectedDate(nd); ensureWeekLoaded(nd) } }
   const handleNext = () => { if (viewMode === 'week') { const d = new Date(weekStart + 'T12:00:00'); d.setDate(d.getDate() + 7); loadWeekData(toLocalDateString(d)) } else { const d = new Date(selectedDate + 'T12:00:00'); d.setDate(d.getDate() + 1); const nd = toLocalDateString(d); setSelectedDate(nd); ensureWeekLoaded(nd) } }
-  const handleToday = () => { setSelectedDate(toLocalDateString(new Date())); loadWeekData(getMonday(new Date())) }
+  const handleToday = () => { setSelectedDate(toLocalDateString(new Date())); loadWeekData(getWeekStart(new Date(), weekStartDay)) }
 
 
   useEffect(() => {
   useEffect(() => {
     function msUntilMidnight() { const now = new Date(); const m = new Date(now); m.setHours(24, 0, 0, 0); return m.getTime() - now.getTime() }
     function msUntilMidnight() { const now = new Date(); const m = new Date(now); m.setHours(24, 0, 0, 0); return m.getTime() - now.getTime() }
     let timeout: ReturnType<typeof setTimeout>
     let timeout: ReturnType<typeof setTimeout>
-    function schedule() { timeout = setTimeout(() => { const now = new Date(); setSelectedDate(toLocalDateString(now)); loadWeekData(getMonday(now)); schedule() }, msUntilMidnight() + 500) }
+    function schedule() { timeout = setTimeout(() => { const now = new Date(); setSelectedDate(toLocalDateString(now)); loadWeekData(getWeekStart(now, weekStartDay)); schedule() }, msUntilMidnight() + 500) }
     schedule(); return () => clearTimeout(timeout)
     schedule(); return () => clearTimeout(timeout)
-  }, [loadWeekData, setSelectedDate])
+  }, [loadWeekData, setSelectedDate, weekStartDay])
 
 
   const dateLabel = viewMode === 'week' ? formatWeekRange(weekStart, locale) : formatDayDate(selectedDate, locale)
   const dateLabel = viewMode === 'week' ? formatWeekRange(weekStart, locale) : formatDayDate(selectedDate, locale)
 
 
@@ -146,9 +146,9 @@ export function WorkBoardPresenter({ initialTechnicians, initialAssignments, ini
         <div className="flex-1 overflow-auto">
         <div className="flex-1 overflow-auto">
           <div className="grid h-full min-w-225" style={{ gridTemplateColumns: '160px repeat(7, 1fr)', gridTemplateRows: `auto ${store.technicians.length > 0 ? `repeat(${store.technicians.length}, 1fr)` : '1fr'}` }}>
           <div className="grid h-full min-w-225" style={{ gridTemplateColumns: '160px repeat(7, 1fr)', gridTemplateRows: `auto ${store.technicians.length > 0 ? `repeat(${store.technicians.length}, 1fr)` : '1fr'}` }}>
             <div className="border-b p-2" />
             <div className="border-b p-2" />
-            {days.map((day, i) => {
+            {days.map((day) => {
               const isToday = day === today; const d = new Date(day + 'T12:00:00');
               const isToday = day === today; const d = new Date(day + 'T12:00:00');
-              return <div key={day} className={`border-b border-l p-2 text-center text-sm font-semibold ${isToday ? 'bg-primary text-primary-foreground' : 'text-muted-foreground'}`}>{tb(`days.${DAY_KEYS[i]}`)} {d.getDate()}/{d.getMonth() + 1}</div>
+              return <div key={day} className={`border-b border-l p-2 text-center text-sm font-semibold ${isToday ? 'bg-primary text-primary-foreground' : 'text-muted-foreground'}`}>{tb(`days.${DAY_KEYS[d.getDay()]}`)} {d.getDate()}/{d.getMonth() + 1}</div>
             })}
             })}
             {store.technicians.map((tech) => {
             {store.technicians.map((tech) => {
               const techJobs = store.jobs.filter((a) => a.technicianId === tech.id)
               const techJobs = store.jobs.filter((a) => a.technicianId === tech.id)

+ 3 - 0
src/lib/get-layout-data.ts

@@ -16,6 +16,7 @@ type AuthResult =
       dateFormat: string | undefined;
       dateFormat: string | undefined;
       timeFormat: string | undefined;
       timeFormat: string | undefined;
       timezone: string | undefined;
       timezone: string | undefined;
+      weekStartDay: number;
       serviceType: string | undefined;
       serviceType: string | undefined;
       currencyCode: string | undefined;
       currencyCode: string | undefined;
       currencyFormat: string | undefined;
       currencyFormat: string | undefined;
@@ -52,6 +53,7 @@ export async function getLayoutData(): Promise<AuthResult> {
                 SETTING_KEYS.DATE_FORMAT,
                 SETTING_KEYS.DATE_FORMAT,
                 SETTING_KEYS.TIME_FORMAT,
                 SETTING_KEYS.TIME_FORMAT,
                 SETTING_KEYS.TIMEZONE,
                 SETTING_KEYS.TIMEZONE,
+                SETTING_KEYS.WORKBOARD_WEEK_START_DAY,
                 SETTING_KEYS.SERVICE_TYPE,
                 SETTING_KEYS.SERVICE_TYPE,
                 SETTING_KEYS.CURRENCY_CODE,
                 SETTING_KEYS.CURRENCY_CODE,
                 SETTING_KEYS.CURRENCY_FORMAT,
                 SETTING_KEYS.CURRENCY_FORMAT,
@@ -83,6 +85,7 @@ export async function getLayoutData(): Promise<AuthResult> {
     dateFormat: orgMap.get(SETTING_KEYS.DATE_FORMAT) || undefined,
     dateFormat: orgMap.get(SETTING_KEYS.DATE_FORMAT) || undefined,
     timeFormat: orgMap.get(SETTING_KEYS.TIME_FORMAT) || undefined,
     timeFormat: orgMap.get(SETTING_KEYS.TIME_FORMAT) || undefined,
     timezone: orgMap.get(SETTING_KEYS.TIMEZONE) || undefined,
     timezone: orgMap.get(SETTING_KEYS.TIMEZONE) || undefined,
+    weekStartDay: parseInt(orgMap.get(SETTING_KEYS.WORKBOARD_WEEK_START_DAY) || "1", 10),
     serviceType: orgMap.get(SETTING_KEYS.SERVICE_TYPE) || undefined,
     serviceType: orgMap.get(SETTING_KEYS.SERVICE_TYPE) || undefined,
     currencyCode: orgMap.get(SETTING_KEYS.CURRENCY_CODE) || undefined,
     currencyCode: orgMap.get(SETTING_KEYS.CURRENCY_CODE) || undefined,
     currencyFormat: orgMap.get(SETTING_KEYS.CURRENCY_FORMAT) || undefined,
     currencyFormat: orgMap.get(SETTING_KEYS.CURRENCY_FORMAT) || undefined,