Ver Fonte

Add bulk assign default technician to unassigned work orders (#104)

* assign tech to all word orders

* add linkt to set default

* trasnslations
Bernt Christian Egeland há 6 meses atrás
pai
commit
ecbde05f09

+ 2 - 1
messages/de/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "Auftrag erstreckt sich über mehrere Tage — Fortsetzung für den nächsten Tag erstellt",
     "splitCreated": "Auftrag erstreckt sich über mehrere Tage — Fortsetzung für den nächsten Tag erstellt",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "Mehr erfahren",
+    "setDefaults": "Standardwerte"
   }
   }
 }
 }

+ 7 - 0
messages/de/settings.json

@@ -691,6 +691,13 @@
     "gallons": "Gallonen (gal)",
     "gallons": "Gallonen (gal)",
     "saveWorkshop": "Werkstatteinstellungen speichern",
     "saveWorkshop": "Werkstatteinstellungen speichern",
     "saved": "Werkstatteinstellungen gespeichert",
     "saved": "Werkstatteinstellungen gespeichert",
+    "assignDialogTitle": "Bestehenden Aufträgen zuweisen?",
+    "assignDialogDescription": "Möchten Sie {name} allen aktuellen Aufträgen zuweisen, die keinen Techniker haben?",
+    "assignDialogConfirm": "Ja, zuweisen",
+    "assignDialogSkip": "Nein, überspringen",
+    "assignedUnassigned": "{count, plural, =0 {Keine nicht zugewiesenen Aufträge gefunden} one {1 Auftrag zugewiesen} other {# Aufträge zugewiesen}}",
+    "assignFailed": "Aufträge konnten nicht zugewiesen werden",
+    "noTechnician": "Keine",
     "weekStartDay": "Wochenstart",
     "weekStartDay": "Wochenstart",
     "workDayStart": "Arbeitsbeginn",
     "workDayStart": "Arbeitsbeginn",
     "workDayEnd": "Arbeitsende",
     "workDayEnd": "Arbeitsende",

+ 2 - 1
messages/en/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "Job spans multiple days — continuation created for next day",
     "splitCreated": "Job spans multiple days — continuation created for next day",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "Read More",
+    "setDefaults": "Set Defaults"
   }
   }
 }
 }

+ 7 - 0
messages/en/settings.json

@@ -691,6 +691,13 @@
     "gallons": "gallons (gal)",
     "gallons": "gallons (gal)",
     "saveWorkshop": "Save Workshop Settings",
     "saveWorkshop": "Save Workshop Settings",
     "saved": "Workshop settings saved",
     "saved": "Workshop settings saved",
+    "assignDialogTitle": "Assign to existing work orders?",
+    "assignDialogDescription": "Would you like to assign {name} to all current work orders that don't have a technician assigned?",
+    "assignDialogConfirm": "Yes, assign",
+    "assignDialogSkip": "No, skip",
+    "assignedUnassigned": "{count, plural, =0 {No unassigned work orders found} one {Assigned 1 work order} other {Assigned # work orders}}",
+    "assignFailed": "Failed to assign work orders",
+    "noTechnician": "None",
     "weekStartDay": "Week Start Day",
     "weekStartDay": "Week Start Day",
     "workDayStart": "Work Day Start",
     "workDayStart": "Work Day Start",
     "workDayEnd": "Work Day End",
     "workDayEnd": "Work Day End",

+ 2 - 1
messages/es/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "El trabajo abarca varios días — continuación creada para el día siguiente",
     "splitCreated": "El trabajo abarca varios días — continuación creada para el día siguiente",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "Leer más",
+    "setDefaults": "Configurar valores"
   }
   }
 }
 }

+ 7 - 0
messages/es/settings.json

@@ -691,6 +691,13 @@
     "gallons": "galones (gal)",
     "gallons": "galones (gal)",
     "saveWorkshop": "Guardar configuracion del taller",
     "saveWorkshop": "Guardar configuracion del taller",
     "saved": "Configuracion del taller guardada",
     "saved": "Configuracion del taller guardada",
+    "assignDialogTitle": "¿Asignar a órdenes de trabajo existentes?",
+    "assignDialogDescription": "¿Desea asignar {name} a todas las órdenes de trabajo que no tienen un técnico asignado?",
+    "assignDialogConfirm": "Sí, asignar",
+    "assignDialogSkip": "No, omitir",
+    "assignedUnassigned": "{count, plural, =0 {No se encontraron órdenes sin asignar} one {Se asignó 1 orden} other {Se asignaron # órdenes}}",
+    "assignFailed": "Error al asignar órdenes de trabajo",
+    "noTechnician": "Ninguno",
     "weekStartDay": "Inicio de semana",
     "weekStartDay": "Inicio de semana",
     "workDayStart": "Inicio de jornada",
     "workDayStart": "Inicio de jornada",
     "workDayEnd": "Fin de jornada",
     "workDayEnd": "Fin de jornada",

+ 2 - 1
messages/fr/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "Le travail s'étend sur plusieurs jours — suite créée pour le jour suivant",
     "splitCreated": "Le travail s'étend sur plusieurs jours — suite créée pour le jour suivant",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "En savoir plus",
+    "setDefaults": "Paramètres par défaut"
   }
   }
 }
 }

+ 7 - 0
messages/fr/settings.json

@@ -691,6 +691,13 @@
     "gallons": "gallons (gal)",
     "gallons": "gallons (gal)",
     "saveWorkshop": "Enregistrer les parametres de l'atelier",
     "saveWorkshop": "Enregistrer les parametres de l'atelier",
     "saved": "Parametres de l'atelier enregistres",
     "saved": "Parametres de l'atelier enregistres",
+    "assignDialogTitle": "Assigner aux ordres de travail existants ?",
+    "assignDialogDescription": "Voulez-vous assigner {name} à tous les ordres de travail qui n'ont pas de technicien ?",
+    "assignDialogConfirm": "Oui, assigner",
+    "assignDialogSkip": "Non, ignorer",
+    "assignedUnassigned": "{count, plural, =0 {Aucun ordre non assigné trouvé} one {1 ordre assigné} other {# ordres assignés}}",
+    "assignFailed": "Échec de l'assignation des ordres",
+    "noTechnician": "Aucun",
     "weekStartDay": "Debut de semaine",
     "weekStartDay": "Debut de semaine",
     "workDayStart": "Debut de journee",
     "workDayStart": "Debut de journee",
     "workDayEnd": "Fin de journee",
     "workDayEnd": "Fin de journee",

+ 2 - 1
messages/it/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "Il lavoro si estende su più giorni — continuazione creata per il giorno successivo",
     "splitCreated": "Il lavoro si estende su più giorni — continuazione creata per il giorno successivo",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "Scopri di più",
+    "setDefaults": "Valori predefiniti"
   }
   }
 }
 }

+ 7 - 0
messages/it/settings.json

@@ -691,6 +691,13 @@
     "gallons": "galloni (gal)",
     "gallons": "galloni (gal)",
     "saveWorkshop": "Salva impostazioni officina",
     "saveWorkshop": "Salva impostazioni officina",
     "saved": "Impostazioni officina salvate",
     "saved": "Impostazioni officina salvate",
+    "assignDialogTitle": "Assegnare agli ordini di lavoro esistenti?",
+    "assignDialogDescription": "Vuoi assegnare {name} a tutti gli ordini di lavoro che non hanno un tecnico assegnato?",
+    "assignDialogConfirm": "Sì, assegna",
+    "assignDialogSkip": "No, salta",
+    "assignedUnassigned": "{count, plural, =0 {Nessun ordine non assegnato trovato} one {1 ordine assegnato} other {# ordini assegnati}}",
+    "assignFailed": "Impossibile assegnare gli ordini di lavoro",
+    "noTechnician": "Nessuno",
     "weekStartDay": "Inizio settimana",
     "weekStartDay": "Inizio settimana",
     "workDayStart": "Inizio giornata",
     "workDayStart": "Inizio giornata",
     "workDayEnd": "Fine giornata",
     "workDayEnd": "Fine giornata",

+ 2 - 1
messages/nb/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "Jobben går over flere dager — fortsettelse opprettet for neste dag",
     "splitCreated": "Jobben går over flere dager — fortsettelse opprettet for neste dag",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "Les mer",
+    "setDefaults": "Standardinnstillinger"
   }
   }
 }
 }

+ 7 - 0
messages/nb/settings.json

@@ -691,6 +691,13 @@
     "gallons": "gallons (gal)",
     "gallons": "gallons (gal)",
     "saveWorkshop": "Lagre verkstedinnstillinger",
     "saveWorkshop": "Lagre verkstedinnstillinger",
     "saved": "Verkstedinnstillinger lagret",
     "saved": "Verkstedinnstillinger lagret",
+    "assignDialogTitle": "Tildel til eksisterende arbeidsordre?",
+    "assignDialogDescription": "Vil du tildele {name} til alle arbeidsordre som ikke har en tekniker tildelt?",
+    "assignDialogConfirm": "Ja, tildel",
+    "assignDialogSkip": "Nei, hopp over",
+    "assignedUnassigned": "{count, plural, =0 {Ingen utildelte arbeidsordre funnet} one {Tildelte 1 arbeidsordre} other {Tildelte # arbeidsordre}}",
+    "assignFailed": "Kunne ikke tildele arbeidsordre",
+    "noTechnician": "Ingen",
     "weekStartDay": "Ukens startdag",
     "weekStartDay": "Ukens startdag",
     "workDayStart": "Arbeidsdagen starter",
     "workDayStart": "Arbeidsdagen starter",
     "workDayEnd": "Arbeidsdagen slutter",
     "workDayEnd": "Arbeidsdagen slutter",

+ 2 - 1
messages/nl/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "Taak beslaat meerdere dagen — vervolg aangemaakt voor de volgende dag",
     "splitCreated": "Taak beslaat meerdere dagen — vervolg aangemaakt voor de volgende dag",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "Meer lezen",
+    "setDefaults": "Standaardwaarden"
   }
   }
 }
 }

+ 7 - 0
messages/nl/settings.json

@@ -691,6 +691,13 @@
     "gallons": "gallons (gal)",
     "gallons": "gallons (gal)",
     "saveWorkshop": "Werkplaatsinstellingen opslaan",
     "saveWorkshop": "Werkplaatsinstellingen opslaan",
     "saved": "Werkplaatsinstellingen opgeslagen",
     "saved": "Werkplaatsinstellingen opgeslagen",
+    "assignDialogTitle": "Toewijzen aan bestaande werkorders?",
+    "assignDialogDescription": "Wilt u {name} toewijzen aan alle werkorders die geen technicus hebben?",
+    "assignDialogConfirm": "Ja, toewijzen",
+    "assignDialogSkip": "Nee, overslaan",
+    "assignedUnassigned": "{count, plural, =0 {Geen niet-toegewezen werkorders gevonden} one {1 werkorder toegewezen} other {# werkorders toegewezen}}",
+    "assignFailed": "Toewijzen van werkorders mislukt",
+    "noTechnician": "Geen",
     "weekStartDay": "Weekstart",
     "weekStartDay": "Weekstart",
     "workDayStart": "Werkdag begin",
     "workDayStart": "Werkdag begin",
     "workDayEnd": "Werkdag einde",
     "workDayEnd": "Werkdag einde",

+ 2 - 1
messages/pl/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "Zadanie obejmuje wiele dni — kontynuacja utworzona na następny dzień",
     "splitCreated": "Zadanie obejmuje wiele dni — kontynuacja utworzona na następny dzień",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "Czytaj więcej",
+    "setDefaults": "Ustaw domyślne"
   }
   }
 }
 }

+ 7 - 0
messages/pl/settings.json

@@ -691,6 +691,13 @@
     "gallons": "galony (gal)",
     "gallons": "galony (gal)",
     "saveWorkshop": "Zapisz ustawienia warsztatu",
     "saveWorkshop": "Zapisz ustawienia warsztatu",
     "saved": "Ustawienia warsztatu zapisane",
     "saved": "Ustawienia warsztatu zapisane",
+    "assignDialogTitle": "Przypisać do istniejących zleceń?",
+    "assignDialogDescription": "Czy chcesz przypisać {name} do wszystkich zleceń, które nie mają przypisanego technika?",
+    "assignDialogConfirm": "Tak, przypisz",
+    "assignDialogSkip": "Nie, pomiń",
+    "assignedUnassigned": "{count, plural, =0 {Nie znaleziono nieprzypisanych zleceń} one {Przypisano 1 zlecenie} other {Przypisano # zleceń}}",
+    "assignFailed": "Nie udało się przypisać zleceń",
+    "noTechnician": "Brak",
     "weekStartDay": "Poczatek tygodnia",
     "weekStartDay": "Poczatek tygodnia",
     "workDayStart": "Poczatek dnia pracy",
     "workDayStart": "Poczatek dnia pracy",
     "workDayEnd": "Koniec dnia pracy",
     "workDayEnd": "Koniec dnia pracy",

+ 2 - 1
messages/pt-BR/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "O trabalho abrange vários dias — continuação criada para o dia seguinte",
     "splitCreated": "O trabalho abrange vários dias — continuação criada para o dia seguinte",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "Saiba mais",
+    "setDefaults": "Valores padrão"
   }
   }
 }
 }

+ 7 - 0
messages/pt-BR/settings.json

@@ -691,6 +691,13 @@
     "gallons": "galoes (gal)",
     "gallons": "galoes (gal)",
     "saveWorkshop": "Salvar configuracoes da oficina",
     "saveWorkshop": "Salvar configuracoes da oficina",
     "saved": "Configuracoes da oficina salvas",
     "saved": "Configuracoes da oficina salvas",
+    "assignDialogTitle": "Atribuir às ordens de serviço existentes?",
+    "assignDialogDescription": "Deseja atribuir {name} a todas as ordens de serviço que não têm um técnico atribuído?",
+    "assignDialogConfirm": "Sim, atribuir",
+    "assignDialogSkip": "Não, pular",
+    "assignedUnassigned": "{count, plural, =0 {Nenhuma ordem sem técnico encontrada} one {1 ordem atribuída} other {# ordens atribuídas}}",
+    "assignFailed": "Falha ao atribuir ordens de serviço",
+    "noTechnician": "Nenhum",
     "weekStartDay": "Inicio da semana",
     "weekStartDay": "Inicio da semana",
     "workDayStart": "Inicio do expediente",
     "workDayStart": "Inicio do expediente",
     "workDayEnd": "Fim do expediente",
     "workDayEnd": "Fim do expediente",

+ 2 - 1
messages/ru/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "Работа занимает несколько дней — создано продолжение на следующий день",
     "splitCreated": "Работа занимает несколько дней — создано продолжение на следующий день",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "Подробнее",
+    "setDefaults": "Настройки по умолчанию"
   }
   }
 }
 }

+ 7 - 0
messages/ru/settings.json

@@ -691,6 +691,13 @@
     "gallons": "галлоны (gal)",
     "gallons": "галлоны (gal)",
     "saveWorkshop": "Сохранить настройки мастерской",
     "saveWorkshop": "Сохранить настройки мастерской",
     "saved": "Настройки мастерской сохранены",
     "saved": "Настройки мастерской сохранены",
+    "assignDialogTitle": "Назначить на существующие заказы?",
+    "assignDialogDescription": "Хотите назначить {name} на все заказы, у которых нет назначенного техника?",
+    "assignDialogConfirm": "Да, назначить",
+    "assignDialogSkip": "Нет, пропустить",
+    "assignedUnassigned": "{count, plural, =0 {Не найдено заказов без техника} one {Назначен 1 заказ} other {Назначено # заказов}}",
+    "assignFailed": "Не удалось назначить заказы",
+    "noTechnician": "Нет",
     "weekStartDay": "Первый день недели",
     "weekStartDay": "Первый день недели",
     "workDayStart": "Начало рабочего дня",
     "workDayStart": "Начало рабочего дня",
     "workDayEnd": "Конец рабочего дня",
     "workDayEnd": "Конец рабочего дня",

+ 2 - 1
messages/tr/service.json

@@ -312,6 +312,7 @@
     "splitCreated": "İş birden fazla güne yayılıyor — sonraki gün için devam oluşturuldu",
     "splitCreated": "İş birden fazla güne yayılıyor — sonraki gün için devam oluşturuldu",
     "platformUsers": "Platform Users",
     "platformUsers": "Platform Users",
     "customTechnicians": "Custom",
     "customTechnicians": "Custom",
-    "readMore": "Read More"
+    "readMore": "Daha fazla",
+    "setDefaults": "Varsayılanlar"
   }
   }
 }
 }

+ 7 - 0
messages/tr/settings.json

@@ -691,6 +691,13 @@
     "gallons": "galon (gal)",
     "gallons": "galon (gal)",
     "saveWorkshop": "Atölye Ayarlarını Kaydet",
     "saveWorkshop": "Atölye Ayarlarını Kaydet",
     "saved": "Atölye ayarları kaydedildi",
     "saved": "Atölye ayarları kaydedildi",
+    "assignDialogTitle": "Mevcut iş emirlerine ata?",
+    "assignDialogDescription": "Teknisyen atanmamış tüm iş emirlerine {name} atamak ister misiniz?",
+    "assignDialogConfirm": "Evet, ata",
+    "assignDialogSkip": "Hayır, atla",
+    "assignedUnassigned": "{count, plural, =0 {Atanmamış iş emri bulunamadı} one {1 iş emri atandı} other {# iş emri atandı}}",
+    "assignFailed": "İş emirleri atanamadı",
+    "noTechnician": "Yok",
     "weekStartDay": "Hafta Baslangici",
     "weekStartDay": "Hafta Baslangici",
     "workDayStart": "Is Gunu Baslangici",
     "workDayStart": "Is Gunu Baslangici",
     "workDayEnd": "Is Gunu Bitisi",
     "workDayEnd": "Is Gunu Bitisi",

+ 68 - 0
src/app/(authenticated)/settings/workshop/workshop-settings.tsx

@@ -18,9 +18,20 @@ import {
   CommandList,
   CommandList,
   CommandSeparator,
   CommandSeparator,
 } from "@/components/ui/command";
 } from "@/components/ui/command";
+import {
+  AlertDialog,
+  AlertDialogAction,
+  AlertDialogCancel,
+  AlertDialogContent,
+  AlertDialogDescription,
+  AlertDialogFooter,
+  AlertDialogHeader,
+  AlertDialogTitle,
+} from "@/components/ui/alert-dialog";
 import { toast } from "sonner";
 import { toast } from "sonner";
 import { setSettings } from "@/features/settings/Actions/settingsActions";
 import { setSettings } from "@/features/settings/Actions/settingsActions";
 import { SETTING_KEYS } from "@/features/settings/Schema/settingsSchema";
 import { SETTING_KEYS } from "@/features/settings/Schema/settingsSchema";
+import { assignTechToUnassignedWorkOrders } from "@/features/workboard/Actions/technicianActions";
 import { Loader2, Ruler, Save, Wrench, Check, ChevronsUpDown, Plus } from "lucide-react";
 import { Loader2, Ruler, Save, Wrench, Check, ChevronsUpDown, Plus } from "lucide-react";
 import {
 import {
   Select,
   Select,
@@ -52,6 +63,9 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
   const [creatingTech, setCreatingTech] = useState(false);
   const [creatingTech, setCreatingTech] = useState(false);
   const [showNewInput, setShowNewInput] = useState(false);
   const [showNewInput, setShowNewInput] = useState(false);
   const [newTechName, setNewTechName] = useState('');
   const [newTechName, setNewTechName] = useState('');
+  const [showAssignDialog, setShowAssignDialog] = useState(false);
+  const [pendingTechId, setPendingTechId] = useState<string | null>(null);
+  const [assigning, setAssigning] = useState(false);
   const [defaultLaborRate, setDefaultLaborRate] = useState(
   const [defaultLaborRate, setDefaultLaborRate] = useState(
     settings[SETTING_KEYS.DEFAULT_LABOR_RATE] || ""
     settings[SETTING_KEYS.DEFAULT_LABOR_RATE] || ""
   );
   );
@@ -73,6 +87,24 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
   const handleTechSelect = (techId: string) => {
   const handleTechSelect = (techId: string) => {
     setDefaultTechnicianId(techId);
     setDefaultTechnicianId(techId);
     setTechOpen(false);
     setTechOpen(false);
+    if (techId) {
+      setPendingTechId(techId);
+      setShowAssignDialog(true);
+    }
+  };
+
+  const handleAssignUnassigned = async () => {
+    if (!pendingTechId) return;
+    setAssigning(true);
+    const result = await assignTechToUnassignedWorkOrders(pendingTechId);
+    setAssigning(false);
+    setShowAssignDialog(false);
+    setPendingTechId(null);
+    if (result.success && result.data) {
+      toast.success(t('workshop.assignedUnassigned', { count: result.data.updated }));
+    } else {
+      toast.error(result.error || t('workshop.assignFailed'));
+    }
   };
   };
 
 
   const doCreateTechnician = async (name: string) => {
   const doCreateTechnician = async (name: string) => {
@@ -152,6 +184,21 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
                     <CommandList className="max-h-60 overflow-y-auto">
                     <CommandList className="max-h-60 overflow-y-auto">
                       <CommandEmpty className="p-0" />
                       <CommandEmpty className="p-0" />
                       <CommandGroup>
                       <CommandGroup>
+                        <CommandItem
+                          value="__none__"
+                          onSelect={() => {
+                            setDefaultTechnicianId("");
+                            setTechOpen(false);
+                          }}
+                        >
+                          <Check
+                            className={cn(
+                              'mr-2 h-4 w-4',
+                              !defaultTechnicianId ? 'opacity-100' : 'opacity-0',
+                            )}
+                          />
+                          <span className="text-muted-foreground">{t('workshop.noTechnician')}</span>
+                        </CommandItem>
                         {technicians.map((tech) => (
                         {technicians.map((tech) => (
                           <CommandItem
                           <CommandItem
                             key={tech.id}
                             key={tech.id}
@@ -330,6 +377,27 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
         </CardContent>
         </CardContent>
       </Card>
       </Card>
       </ReadOnlyWrapper>
       </ReadOnlyWrapper>
+
+      <AlertDialog open={showAssignDialog} onOpenChange={(open) => {
+        if (!open) setPendingTechId(null);
+        setShowAssignDialog(open);
+      }}>
+        <AlertDialogContent>
+          <AlertDialogHeader>
+            <AlertDialogTitle>{t('workshop.assignDialogTitle')}</AlertDialogTitle>
+            <AlertDialogDescription>
+              {t('workshop.assignDialogDescription', { name: technicians.find((tech) => tech.id === pendingTechId)?.name || '' })}
+            </AlertDialogDescription>
+          </AlertDialogHeader>
+          <AlertDialogFooter>
+            <AlertDialogCancel>{t('workshop.assignDialogSkip')}</AlertDialogCancel>
+            <AlertDialogAction onClick={handleAssignUnassigned} disabled={assigning}>
+              {assigning && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+              {t('workshop.assignDialogConfirm')}
+            </AlertDialogAction>
+          </AlertDialogFooter>
+        </AlertDialogContent>
+      </AlertDialog>
     </div>
     </div>
   );
   );
 }
 }

+ 13 - 2
src/features/vehicles/Components/service-edit/ScheduleTimesSection.tsx

@@ -15,7 +15,7 @@ import {
   CommandSeparator,
   CommandSeparator,
 } from '@/components/ui/command'
 } from '@/components/ui/command'
 import { Input } from '@/components/ui/input'
 import { Input } from '@/components/ui/input'
-import { Check, ChevronsUpDown, Clock, Plus } from 'lucide-react'
+import { Check, ChevronsUpDown, Clock, Plus, Settings } from 'lucide-react'
 import { toast } from 'sonner'
 import { toast } from 'sonner'
 import { DateTimePicker } from '@/components/ui/datetime-picker'
 import { DateTimePicker } from '@/components/ui/datetime-picker'
 import {
 import {
@@ -24,6 +24,7 @@ import {
 } from '@/features/workboard/Actions/boardActions'
 } from '@/features/workboard/Actions/boardActions'
 import { createTechnician } from '@/features/workboard/Actions/technicianActions'
 import { createTechnician } from '@/features/workboard/Actions/technicianActions'
 import { cn } from '@/lib/utils'
 import { cn } from '@/lib/utils'
+import Link from 'next/link'
 
 
 const HOUR_PRESETS = [1, 2, 4, 5, 7]
 const HOUR_PRESETS = [1, 2, 4, 5, 7]
 
 
@@ -188,7 +189,17 @@ export function ScheduleTimesSection({
       </div>
       </div>
 
 
       <div className="space-y-1">
       <div className="space-y-1">
-        <Label className="text-xs">{t('technician')}</Label>
+        <div className="flex items-center justify-between">
+          <Label className="text-xs">{t('technician')}</Label>
+          <Link
+            href="/settings/workshop"
+            target="_blank"
+            className="inline-flex items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground transition-colors"
+          >
+            <Settings className="h-3 w-3" />
+            {t('setDefaults')}
+          </Link>
+        </div>
         <Popover open={techOpen} onOpenChange={setTechOpen} modal={true}>
         <Popover open={techOpen} onOpenChange={setTechOpen} modal={true}>
           <PopoverTrigger asChild>
           <PopoverTrigger asChild>
             <Button
             <Button

+ 32 - 0
src/features/workboard/Actions/technicianActions.ts

@@ -174,6 +174,38 @@ export async function deleteTechnician(id: string) {
   );
   );
 }
 }
 
 
+export async function assignTechToUnassignedWorkOrders(technicianId: string) {
+  return withAuth(
+    async ({ organizationId }) => {
+      const technician = await db.technician.findFirst({
+        where: { id: technicianId, organizationId, isActive: true },
+        select: { id: true, name: true },
+      });
+
+      if (!technician) throw new Error("Technician not found");
+
+      const result = await db.serviceRecord.updateMany({
+        where: {
+          vehicle: { organizationId },
+          technicianId: null,
+        },
+        data: {
+          technicianId: technician.id,
+          techName: technician.name,
+        },
+      });
+
+      revalidatePath("/work-board");
+      return { updated: result.count };
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.SERVICES },
+      ],
+    },
+  );
+}
+
 export async function getOrgMembers() {
 export async function getOrgMembers() {
   return withAuth(async ({ organizationId }) => {
   return withAuth(async ({ organizationId }) => {
     const members = await db.organizationMember.findMany({
     const members = await db.organizationMember.findMany({