Просмотр исходного кода

Improved templates and custom fields (#52)

* sms escape string

* editing header section

* hydration

* lint

* removed unused section

* moved week start to workshop section

* translations

* card link

* grid

* hydration
Bernt Christian Egeland 6 месяцев назад
Родитель
Сommit
1ebcd26379

+ 26 - 6
messages/de/settings.json

@@ -386,6 +386,10 @@
       "tech_name": "Techniker",
       "logo": "Logo",
       "company_name": "Firmenname",
+      "company_address": "Adresse",
+      "company_phone": "Telefon",
+      "company_email": "E-Mail",
+      "company_org_number": "Organisationsnummer",
       "bank_account": "Bankkonto",
       "org_number": "Organisationsnummer"
     }
@@ -399,7 +403,8 @@
       "invoice": "Rechnung",
       "quotation": "Angebot",
       "inspections": "Inspektionen",
-      "sms": "SMS"
+      "sms": "SMS",
+      "layout": "Layout"
     },
     "presets": "Vorlagen-Presets",
     "primaryColor": "Primarfarbe",
@@ -438,7 +443,7 @@
     "quotationTemplateSaved": "Angebotsvorlageneinstellungen gespeichert",
     "failedSave": "Einstellungen konnten nicht gespeichert werden",
     "smsTemplates": "SMS-Nachrichtenvorlagen",
-    "smsTemplatesDescription": "Passen Sie die Textnachrichten an Kunden an. Verwenden Sie Variablen wie <code>{companyName}</code>, um dynamische Werte einzufugen.",
+    "smsTemplatesDescription": "Passen Sie die Textnachrichten an Kunden an. Verwenden Sie Variablen wie <code>'{companyName}'</code>, um dynamische Werte einzufugen.",
     "reset": "Zurucksetzen",
     "invoiceReady": "Rechnung bereit",
     "invoiceReadyDescription": "Wird gesendet, wenn eine Rechnung mit einem Kunden geteilt wird.",
@@ -477,7 +482,12 @@
       "slate": "Schiefer",
       "rose": "Rose",
       "indigo": "Indigo"
-    }
+    },
+    "layoutTitle": "Rechnungslayout",
+    "layoutDescription": "Passen Sie die Reihenfolge und Sichtbarkeit der Abschnitte in Ihren Rechnungen und Angeboten an.",
+    "layoutSaved": "Layout erfolgreich gespeichert",
+    "layoutDocInvoice": "Rechnung",
+    "layoutDocQuote": "Angebot"
   },
   "payment": {
     "detailsTitle": "Zahlungsdetails",
@@ -618,8 +628,6 @@
     "addNewTech": "+ Neu hinzufügen",
     "defaultLaborRate": "Standard-Stundensatz",
     "laborRatePlaceholder": "75,00",
-    "workingHours": "Arbeitszeiten",
-    "workingHoursPlaceholder": "Mo-Fr 8:00 - 17:00",
     "unitsTitle": "Einheiten",
     "unitsDescription": "Wahlen Sie zwischen metrisch (km, Liter) und imperial (Meilen, Gallonen).",
     "unitSystem": "Einheitensystem",
@@ -632,7 +640,19 @@
     "miles": "Meilen (mi)",
     "gallons": "Gallonen (gal)",
     "saveWorkshop": "Werkstatteinstellungen speichern",
-    "saved": "Werkstatteinstellungen gespeichert"
+    "saved": "Werkstatteinstellungen gespeichert",
+    "weekStartDay": "Wochenstart",
+    "workDayStart": "Arbeitsbeginn",
+    "workDayEnd": "Arbeitsende",
+    "weekDays": {
+      "0": "Sonntag",
+      "1": "Montag",
+      "2": "Dienstag",
+      "3": "Mittwoch",
+      "4": "Donnerstag",
+      "5": "Freitag",
+      "6": "Samstag"
+    }
   },
   "maintenance": {
     "title": "Vorhergesagte Wartung",

+ 5 - 5
messages/en/settings.json

@@ -332,6 +332,10 @@
       "tech_name": "Technician",
       "logo": "Logo",
       "company_name": "Company Name",
+      "company_address": "Address",
+      "company_phone": "Phone",
+      "company_email": "Email",
+      "company_org_number": "Organization Number",
       "bank_account": "Bank Account",
       "org_number": "Organization Number"
     }
@@ -385,7 +389,7 @@
     "quotationTemplateSaved": "Quotation template settings saved",
     "failedSave": "Failed to save settings",
     "smsTemplates": "SMS Message Templates",
-    "smsTemplatesDescription": "Customize the text messages sent to customers. Use variables like <code>{companyName}</code> to insert dynamic values.",
+    "smsTemplatesDescription": "Customize the text messages sent to customers. Use variables like <code>'{companyName}'</code> to insert dynamic values.",
     "reset": "Reset",
     "invoiceReady": "Invoice Ready",
     "invoiceReadyDescription": "Sent when sharing an invoice with a customer.",
@@ -570,8 +574,6 @@
     "addNewTech": "+ Add New",
     "defaultLaborRate": "Default Labor Rate (per hour)",
     "laborRatePlaceholder": "75.00",
-    "workingHours": "Working Hours",
-    "workingHoursPlaceholder": "Mon-Fri 8:00 AM - 5:00 PM",
     "unitsTitle": "Units",
     "unitsDescription": "Choose between metric (km, liters) and imperial (miles, gallons) units.",
     "unitSystem": "Unit System",
@@ -585,8 +587,6 @@
     "gallons": "gallons (gal)",
     "saveWorkshop": "Save Workshop Settings",
     "saved": "Workshop settings saved",
-    "workBoardTitle": "Work Board Settings",
-    "workBoardDescription": "Configure the work board schedule and day view timeline.",
     "weekStartDay": "Week Start Day",
     "workDayStart": "Work Day Start",
     "workDayEnd": "Work Day End",

+ 26 - 6
messages/es/settings.json

@@ -386,6 +386,10 @@
       "tech_name": "Técnico",
       "logo": "Logo",
       "company_name": "Nombre de empresa",
+      "company_address": "Dirección",
+      "company_phone": "Teléfono",
+      "company_email": "Correo electrónico",
+      "company_org_number": "Número de organización",
       "bank_account": "Cuenta bancaria",
       "org_number": "Número de organización"
     }
@@ -399,7 +403,8 @@
       "invoice": "Factura",
       "quotation": "Cotizacion",
       "inspections": "Inspecciones",
-      "sms": "SMS"
+      "sms": "SMS",
+      "layout": "Disposicion"
     },
     "presets": "Presets de plantilla",
     "primaryColor": "Color primario",
@@ -438,7 +443,7 @@
     "quotationTemplateSaved": "Configuracion de plantilla de cotizacion guardada",
     "failedSave": "Error al guardar la configuracion",
     "smsTemplates": "Plantillas de mensajes SMS",
-    "smsTemplatesDescription": "Personalice los mensajes de texto enviados a los clientes. Use variables como <code>{companyName}</code> para insertar valores dinamicos.",
+    "smsTemplatesDescription": "Personalice los mensajes de texto enviados a los clientes. Use variables como <code>'{companyName}'</code> para insertar valores dinamicos.",
     "reset": "Restablecer",
     "invoiceReady": "Factura lista",
     "invoiceReadyDescription": "Se envia al compartir una factura con un cliente.",
@@ -477,7 +482,12 @@
       "slate": "Pizarra",
       "rose": "Rosa",
       "indigo": "Indigo"
-    }
+    },
+    "layoutTitle": "Disposicion de factura",
+    "layoutDescription": "Personaliza el orden y la visibilidad de las secciones en tus facturas y presupuestos.",
+    "layoutSaved": "Disposicion guardada correctamente",
+    "layoutDocInvoice": "Factura",
+    "layoutDocQuote": "Presupuesto"
   },
   "payment": {
     "detailsTitle": "Detalles de pago",
@@ -618,8 +628,6 @@
     "addNewTech": "+ Agregar nuevo",
     "defaultLaborRate": "Tarifa de mano de obra predeterminada (por hora)",
     "laborRatePlaceholder": "75.00",
-    "workingHours": "Horario de trabajo",
-    "workingHoursPlaceholder": "Lun-Vie 8:00 AM - 5:00 PM",
     "unitsTitle": "Unidades",
     "unitsDescription": "Elija entre metrico (km, litros) e imperial (millas, galones).",
     "unitSystem": "Sistema de unidades",
@@ -632,7 +640,19 @@
     "miles": "millas (mi)",
     "gallons": "galones (gal)",
     "saveWorkshop": "Guardar configuracion del taller",
-    "saved": "Configuracion del taller guardada"
+    "saved": "Configuracion del taller guardada",
+    "weekStartDay": "Inicio de semana",
+    "workDayStart": "Inicio de jornada",
+    "workDayEnd": "Fin de jornada",
+    "weekDays": {
+      "0": "Domingo",
+      "1": "Lunes",
+      "2": "Martes",
+      "3": "Miercoles",
+      "4": "Jueves",
+      "5": "Viernes",
+      "6": "Sabado"
+    }
   },
   "maintenance": {
     "title": "Mantenimiento predictivo",

+ 26 - 6
messages/fr/settings.json

@@ -386,6 +386,10 @@
       "tech_name": "Technicien",
       "logo": "Logo",
       "company_name": "Nom de l'entreprise",
+      "company_address": "Adresse",
+      "company_phone": "Téléphone",
+      "company_email": "E-mail",
+      "company_org_number": "Numéro d'organisation",
       "bank_account": "Compte bancaire",
       "org_number": "Numéro d'organisation"
     }
@@ -399,7 +403,8 @@
       "invoice": "Facture",
       "quotation": "Devis",
       "inspections": "Inspections",
-      "sms": "SMS"
+      "sms": "SMS",
+      "layout": "Mise en page"
     },
     "presets": "Presets de modele",
     "primaryColor": "Couleur primaire",
@@ -438,7 +443,7 @@
     "quotationTemplateSaved": "Parametres du modele de devis enregistres",
     "failedSave": "Echec de l'enregistrement des parametres",
     "smsTemplates": "Modeles de messages SMS",
-    "smsTemplatesDescription": "Personnalisez les messages texte envoyes aux clients. Utilisez des variables comme <code>{companyName}</code> pour inserer des valeurs dynamiques.",
+    "smsTemplatesDescription": "Personnalisez les messages texte envoyes aux clients. Utilisez des variables comme <code>'{companyName}'</code> pour inserer des valeurs dynamiques.",
     "reset": "Reinitialiser",
     "invoiceReady": "Facture prete",
     "invoiceReadyDescription": "Envoye lors du partage d'une facture avec un client.",
@@ -477,7 +482,12 @@
       "slate": "Ardoise",
       "rose": "Rose",
       "indigo": "Indigo"
-    }
+    },
+    "layoutTitle": "Mise en page de la facture",
+    "layoutDescription": "Personnalisez l'ordre et la visibilite des sections dans vos factures et devis.",
+    "layoutSaved": "Mise en page enregistree",
+    "layoutDocInvoice": "Facture",
+    "layoutDocQuote": "Devis"
   },
   "payment": {
     "detailsTitle": "Details de paiement",
@@ -618,8 +628,6 @@
     "addNewTech": "+ Ajouter nouveau",
     "defaultLaborRate": "Taux horaire par defaut",
     "laborRatePlaceholder": "75,00",
-    "workingHours": "Heures de travail",
-    "workingHoursPlaceholder": "Lun-Ven 8h00 - 17h00",
     "unitsTitle": "Unites",
     "unitsDescription": "Choisissez entre metrique (km, litres) et imperial (miles, gallons).",
     "unitSystem": "Systeme d'unites",
@@ -632,7 +640,19 @@
     "miles": "miles (mi)",
     "gallons": "gallons (gal)",
     "saveWorkshop": "Enregistrer les parametres de l'atelier",
-    "saved": "Parametres de l'atelier enregistres"
+    "saved": "Parametres de l'atelier enregistres",
+    "weekStartDay": "Debut de semaine",
+    "workDayStart": "Debut de journee",
+    "workDayEnd": "Fin de journee",
+    "weekDays": {
+      "0": "Dimanche",
+      "1": "Lundi",
+      "2": "Mardi",
+      "3": "Mercredi",
+      "4": "Jeudi",
+      "5": "Vendredi",
+      "6": "Samedi"
+    }
   },
   "maintenance": {
     "title": "Maintenance predictive",

+ 26 - 6
messages/it/settings.json

@@ -386,6 +386,10 @@
       "tech_name": "Tecnico",
       "logo": "Logo",
       "company_name": "Nome azienda",
+      "company_address": "Indirizzo",
+      "company_phone": "Telefono",
+      "company_email": "E-mail",
+      "company_org_number": "Numero organizzazione",
       "bank_account": "Conto bancario",
       "org_number": "Numero organizzazione"
     }
@@ -399,7 +403,8 @@
       "invoice": "Fattura",
       "quotation": "Preventivo",
       "inspections": "Ispezioni",
-      "sms": "SMS"
+      "sms": "SMS",
+      "layout": "Layout"
     },
     "presets": "Preset modello",
     "primaryColor": "Colore primario",
@@ -438,7 +443,7 @@
     "quotationTemplateSaved": "Impostazioni modello preventivo salvate",
     "failedSave": "Salvataggio impostazioni non riuscito",
     "smsTemplates": "Modelli messaggi SMS",
-    "smsTemplatesDescription": "Personalizza i messaggi di testo inviati ai clienti. Usa variabili come <code>{companyName}</code> per inserire valori dinamici.",
+    "smsTemplatesDescription": "Personalizza i messaggi di testo inviati ai clienti. Usa variabili come <code>'{companyName}'</code> per inserire valori dinamici.",
     "reset": "Ripristina",
     "invoiceReady": "Fattura pronta",
     "invoiceReadyDescription": "Inviato quando si condivide una fattura con un cliente.",
@@ -477,7 +482,12 @@
       "slate": "Ardesia",
       "rose": "Rosa",
       "indigo": "Indaco"
-    }
+    },
+    "layoutTitle": "Layout fattura",
+    "layoutDescription": "Personalizza l'ordine e la visibilita delle sezioni nelle fatture e nei preventivi.",
+    "layoutSaved": "Layout salvato con successo",
+    "layoutDocInvoice": "Fattura",
+    "layoutDocQuote": "Preventivo"
   },
   "payment": {
     "detailsTitle": "Dettagli pagamento",
@@ -618,8 +628,6 @@
     "addNewTech": "+ Aggiungi nuovo",
     "defaultLaborRate": "Tariffa manodopera predefinita (all'ora)",
     "laborRatePlaceholder": "75,00",
-    "workingHours": "Orario di lavoro",
-    "workingHoursPlaceholder": "Lun-Ven 8:00 - 17:00",
     "unitsTitle": "Unita",
     "unitsDescription": "Scegli tra sistema metrico (km, litri) e imperiale (miglia, galloni).",
     "unitSystem": "Sistema di unita",
@@ -632,7 +640,19 @@
     "miles": "miglia (mi)",
     "gallons": "galloni (gal)",
     "saveWorkshop": "Salva impostazioni officina",
-    "saved": "Impostazioni officina salvate"
+    "saved": "Impostazioni officina salvate",
+    "weekStartDay": "Inizio settimana",
+    "workDayStart": "Inizio giornata",
+    "workDayEnd": "Fine giornata",
+    "weekDays": {
+      "0": "Domenica",
+      "1": "Lunedi",
+      "2": "Martedi",
+      "3": "Mercoledi",
+      "4": "Giovedi",
+      "5": "Venerdi",
+      "6": "Sabato"
+    }
   },
   "maintenance": {
     "title": "Manutenzione prevista",

+ 5 - 5
messages/nb/settings.json

@@ -332,6 +332,10 @@
       "tech_name": "Tekniker",
       "logo": "Logo",
       "company_name": "Firmanavn",
+      "company_address": "Adresse",
+      "company_phone": "Telefon",
+      "company_email": "E-post",
+      "company_org_number": "Organisasjonsnummer",
       "bank_account": "Bankkonto",
       "org_number": "Organisasjonsnummer"
     }
@@ -385,7 +389,7 @@
     "quotationTemplateSaved": "Tilbudsmalinnstillinger lagret",
     "failedSave": "Kunne ikke lagre innstillinger",
     "smsTemplates": "SMS-meldingsmaler",
-    "smsTemplatesDescription": "Tilpass tekstmeldingene som sendes til kunder. Bruk variabler som <code>{companyName}</code> for a sette inn dynamiske verdier.",
+    "smsTemplatesDescription": "Tilpass tekstmeldingene som sendes til kunder. Bruk variabler som <code>'{companyName}'</code> for a sette inn dynamiske verdier.",
     "reset": "Tilbakestill",
     "invoiceReady": "Faktura klar",
     "invoiceReadyDescription": "Sendes nar en faktura deles med en kunde.",
@@ -570,8 +574,6 @@
     "addNewTech": "+ Legg til ny",
     "defaultLaborRate": "Standard timesats",
     "laborRatePlaceholder": "750",
-    "workingHours": "Arbeidstider",
-    "workingHoursPlaceholder": "Man-Fre 08:00 - 16:00",
     "unitsTitle": "Enheter",
     "unitsDescription": "Velg mellom metrisk (km, liter) og imperial (miles, gallons).",
     "unitSystem": "Enhetssystem",
@@ -585,8 +587,6 @@
     "gallons": "gallons (gal)",
     "saveWorkshop": "Lagre verkstedinnstillinger",
     "saved": "Verkstedinnstillinger lagret",
-    "workBoardTitle": "Arbeidstavle-innstillinger",
-    "workBoardDescription": "Konfigurer arbeidstavlens tidsplan og dagvisning.",
     "weekStartDay": "Ukens startdag",
     "workDayStart": "Arbeidsdagen starter",
     "workDayEnd": "Arbeidsdagen slutter",

+ 26 - 6
messages/nl/settings.json

@@ -386,6 +386,10 @@
       "tech_name": "Monteur",
       "logo": "Logo",
       "company_name": "Bedrijfsnaam",
+      "company_address": "Adres",
+      "company_phone": "Telefoon",
+      "company_email": "E-mail",
+      "company_org_number": "Organisatienummer",
       "bank_account": "Bankrekening",
       "org_number": "Organisatienummer"
     }
@@ -399,7 +403,8 @@
       "invoice": "Factuur",
       "quotation": "Offerte",
       "inspections": "Inspecties",
-      "sms": "SMS"
+      "sms": "SMS",
+      "layout": "Indeling"
     },
     "presets": "Sjabloonpresets",
     "primaryColor": "Primaire kleur",
@@ -438,7 +443,7 @@
     "quotationTemplateSaved": "Offertesjablooninstellingen opgeslagen",
     "failedSave": "Instellingen opslaan mislukt",
     "smsTemplates": "SMS-berichtsjablonen",
-    "smsTemplatesDescription": "Pas de tekstberichten aan die naar klanten worden gestuurd. Gebruik variabelen zoals <code>{companyName}</code> om dynamische waarden in te voegen.",
+    "smsTemplatesDescription": "Pas de tekstberichten aan die naar klanten worden gestuurd. Gebruik variabelen zoals <code>'{companyName}'</code> om dynamische waarden in te voegen.",
     "reset": "Resetten",
     "invoiceReady": "Factuur gereed",
     "invoiceReadyDescription": "Verzonden bij het delen van een factuur met een klant.",
@@ -477,7 +482,12 @@
       "slate": "Leisteen",
       "rose": "Roze",
       "indigo": "Indigo"
-    }
+    },
+    "layoutTitle": "Factuurindeling",
+    "layoutDescription": "Pas de volgorde en zichtbaarheid van secties in uw facturen en offertes aan.",
+    "layoutSaved": "Indeling succesvol opgeslagen",
+    "layoutDocInvoice": "Factuur",
+    "layoutDocQuote": "Offerte"
   },
   "payment": {
     "detailsTitle": "Betalingsgegevens",
@@ -618,8 +628,6 @@
     "addNewTech": "+ Nieuw toevoegen",
     "defaultLaborRate": "Standaard uurtarief",
     "laborRatePlaceholder": "75,00",
-    "workingHours": "Werkuren",
-    "workingHoursPlaceholder": "Ma-Vr 8:00 - 17:00",
     "unitsTitle": "Eenheden",
     "unitsDescription": "Kies tussen metrisch (km, liters) en imperiaal (mijlen, gallons).",
     "unitSystem": "Eenhedensysteem",
@@ -632,7 +640,19 @@
     "miles": "mijlen (mi)",
     "gallons": "gallons (gal)",
     "saveWorkshop": "Werkplaatsinstellingen opslaan",
-    "saved": "Werkplaatsinstellingen opgeslagen"
+    "saved": "Werkplaatsinstellingen opgeslagen",
+    "weekStartDay": "Weekstart",
+    "workDayStart": "Werkdag begin",
+    "workDayEnd": "Werkdag einde",
+    "weekDays": {
+      "0": "Zondag",
+      "1": "Maandag",
+      "2": "Dinsdag",
+      "3": "Woensdag",
+      "4": "Donderdag",
+      "5": "Vrijdag",
+      "6": "Zaterdag"
+    }
   },
   "maintenance": {
     "title": "Voorspeld onderhoud",

+ 26 - 6
messages/pl/settings.json

@@ -386,6 +386,10 @@
       "tech_name": "Technik",
       "logo": "Logo",
       "company_name": "Nazwa firmy",
+      "company_address": "Adres",
+      "company_phone": "Telefon",
+      "company_email": "E-mail",
+      "company_org_number": "Numer organizacji",
       "bank_account": "Konto bankowe",
       "org_number": "Numer organizacji"
     }
@@ -399,7 +403,8 @@
       "invoice": "Faktura",
       "quotation": "Wycena",
       "inspections": "Inspekcje",
-      "sms": "SMS"
+      "sms": "SMS",
+      "layout": "Uklad"
     },
     "presets": "Gotowe szablony",
     "primaryColor": "Kolor glowny",
@@ -438,7 +443,7 @@
     "quotationTemplateSaved": "Ustawienia szablonu wyceny zapisane",
     "failedSave": "Nie udalo sie zapisac ustawien",
     "smsTemplates": "Szablony wiadomosci SMS",
-    "smsTemplatesDescription": "Dostosuj wiadomosci tekstowe wysylane do klientow. Uzyj zmiennych jak <code>{companyName}</code> aby wstawic dynamiczne wartosci.",
+    "smsTemplatesDescription": "Dostosuj wiadomosci tekstowe wysylane do klientow. Uzyj zmiennych jak <code>'{companyName}'</code> aby wstawic dynamiczne wartosci.",
     "reset": "Resetuj",
     "invoiceReady": "Faktura gotowa",
     "invoiceReadyDescription": "Wysylana przy udostepnianiu faktury klientowi.",
@@ -477,7 +482,12 @@
       "slate": "Lupkowy",
       "rose": "Rozowy",
       "indigo": "Indygo"
-    }
+    },
+    "layoutTitle": "Uklad faktury",
+    "layoutDescription": "Dostosuj kolejnosc i widocznosc sekcji w fakturach i wycenach.",
+    "layoutSaved": "Uklad zapisany pomyslnie",
+    "layoutDocInvoice": "Faktura",
+    "layoutDocQuote": "Wycena"
   },
   "payment": {
     "detailsTitle": "Szczegoly platnosci",
@@ -618,8 +628,6 @@
     "addNewTech": "+ Dodaj nowego",
     "defaultLaborRate": "Domyslna stawka za godzine",
     "laborRatePlaceholder": "75,00",
-    "workingHours": "Godziny pracy",
-    "workingHoursPlaceholder": "Pon-Pt 8:00 - 17:00",
     "unitsTitle": "Jednostki",
     "unitsDescription": "Wybierz miedzy metrycznym (km, litry) a imperialnym (mile, galony).",
     "unitSystem": "System jednostek",
@@ -632,7 +640,19 @@
     "miles": "mile (mi)",
     "gallons": "galony (gal)",
     "saveWorkshop": "Zapisz ustawienia warsztatu",
-    "saved": "Ustawienia warsztatu zapisane"
+    "saved": "Ustawienia warsztatu zapisane",
+    "weekStartDay": "Poczatek tygodnia",
+    "workDayStart": "Poczatek dnia pracy",
+    "workDayEnd": "Koniec dnia pracy",
+    "weekDays": {
+      "0": "Niedziela",
+      "1": "Poniedzialek",
+      "2": "Wtorek",
+      "3": "Sroda",
+      "4": "Czwartek",
+      "5": "Piatek",
+      "6": "Sobota"
+    }
   },
   "maintenance": {
     "title": "Przewidywana konserwacja",

+ 26 - 6
messages/pt-BR/settings.json

@@ -386,6 +386,10 @@
       "tech_name": "Técnico",
       "logo": "Logo",
       "company_name": "Nome da empresa",
+      "company_address": "Endereço",
+      "company_phone": "Telefone",
+      "company_email": "E-mail",
+      "company_org_number": "Número da organização",
       "bank_account": "Conta bancária",
       "org_number": "Número da organização"
     }
@@ -399,7 +403,8 @@
       "invoice": "Fatura",
       "quotation": "Orcamento",
       "inspections": "Inspecoes",
-      "sms": "SMS"
+      "sms": "SMS",
+      "layout": "Layout"
     },
     "presets": "Presets de modelo",
     "primaryColor": "Cor primaria",
@@ -438,7 +443,7 @@
     "quotationTemplateSaved": "Configuracoes do modelo de orcamento salvas",
     "failedSave": "Falha ao salvar configuracoes",
     "smsTemplates": "Modelos de mensagens SMS",
-    "smsTemplatesDescription": "Personalize as mensagens de texto enviadas aos clientes. Use variaveis como <code>{companyName}</code> para inserir valores dinamicos.",
+    "smsTemplatesDescription": "Personalize as mensagens de texto enviadas aos clientes. Use variaveis como <code>'{companyName}'</code> para inserir valores dinamicos.",
     "reset": "Redefinir",
     "invoiceReady": "Fatura pronta",
     "invoiceReadyDescription": "Enviada ao compartilhar uma fatura com um cliente.",
@@ -477,7 +482,12 @@
       "slate": "Ardosia",
       "rose": "Rosa",
       "indigo": "Indigo"
-    }
+    },
+    "layoutTitle": "Layout da fatura",
+    "layoutDescription": "Personalize a ordem e visibilidade das secoes nas suas faturas e orcamentos.",
+    "layoutSaved": "Layout salvo com sucesso",
+    "layoutDocInvoice": "Fatura",
+    "layoutDocQuote": "Orcamento"
   },
   "payment": {
     "detailsTitle": "Detalhes de pagamento",
@@ -618,8 +628,6 @@
     "addNewTech": "+ Adicionar novo",
     "defaultLaborRate": "Taxa de mao de obra padrao (por hora)",
     "laborRatePlaceholder": "75,00",
-    "workingHours": "Horario de funcionamento",
-    "workingHoursPlaceholder": "Seg-Sex 8:00 - 17:00",
     "unitsTitle": "Unidades",
     "unitsDescription": "Escolha entre metrico (km, litros) e imperial (milhas, galoes).",
     "unitSystem": "Sistema de unidades",
@@ -632,7 +640,19 @@
     "miles": "milhas (mi)",
     "gallons": "galoes (gal)",
     "saveWorkshop": "Salvar configuracoes da oficina",
-    "saved": "Configuracoes da oficina salvas"
+    "saved": "Configuracoes da oficina salvas",
+    "weekStartDay": "Inicio da semana",
+    "workDayStart": "Inicio do expediente",
+    "workDayEnd": "Fim do expediente",
+    "weekDays": {
+      "0": "Domingo",
+      "1": "Segunda-feira",
+      "2": "Terca-feira",
+      "3": "Quarta-feira",
+      "4": "Quinta-feira",
+      "5": "Sexta-feira",
+      "6": "Sabado"
+    }
   },
   "maintenance": {
     "title": "Manutencao preditiva",

+ 26 - 6
messages/tr/settings.json

@@ -386,6 +386,10 @@
       "tech_name": "Teknisyen",
       "logo": "Logo",
       "company_name": "Şirket adı",
+      "company_address": "Adres",
+      "company_phone": "Telefon",
+      "company_email": "E-posta",
+      "company_org_number": "Kuruluş numarası",
       "bank_account": "Banka hesabı",
       "org_number": "Kuruluş numarası"
     }
@@ -399,7 +403,8 @@
       "invoice": "Fatura",
       "quotation": "Teklif",
       "inspections": "Muayeneler",
-      "sms": "SMS"
+      "sms": "SMS",
+      "layout": "Duzen"
     },
     "presets": "Şablon Hazır Ayarları",
     "primaryColor": "Ana Renk",
@@ -438,7 +443,7 @@
     "quotationTemplateSaved": "Teklif şablon ayarları kaydedildi",
     "failedSave": "Ayarlar kaydedilemedi",
     "smsTemplates": "SMS Mesaj Şablonları",
-    "smsTemplatesDescription": "Müşterilere gönderilen metin mesajlarını özelleştirin. Dinamik değerler eklemek için <code>{companyName}</code> gibi değişkenler kullanın.",
+    "smsTemplatesDescription": "Müşterilere gönderilen metin mesajlarını özelleştirin. Dinamik değerler eklemek için <code>'{companyName}'</code> gibi değişkenler kullanın.",
     "reset": "Sıfırla",
     "invoiceReady": "Fatura Hazır",
     "invoiceReadyDescription": "Bir fatura müşteriyle paylaşıldığında gönderilir.",
@@ -477,7 +482,12 @@
       "slate": "Kurşuni",
       "rose": "Gül",
       "indigo": "İndigo"
-    }
+    },
+    "layoutTitle": "Fatura Duzeni",
+    "layoutDescription": "Fatura ve tekliflerinizdeki bolumlerin sirasini ve gorunurlugunu ozellestirin.",
+    "layoutSaved": "Duzen basariyla kaydedildi",
+    "layoutDocInvoice": "Fatura",
+    "layoutDocQuote": "Teklif"
   },
   "payment": {
     "detailsTitle": "Ödeme Bilgileri",
@@ -618,8 +628,6 @@
     "addNewTech": "+ Yeni ekle",
     "defaultLaborRate": "Varsayılan İşçilik Ücreti (saat başı)",
     "laborRatePlaceholder": "75,00",
-    "workingHours": "Çalışma Saatleri",
-    "workingHoursPlaceholder": "Pzt-Cum 08:00 - 17:00",
     "unitsTitle": "Birimler",
     "unitsDescription": "Metrik (km, litre) ve emperyal (mil, galon) birimler arasında seçim yapın.",
     "unitSystem": "Birim Sistemi",
@@ -632,7 +640,19 @@
     "miles": "mil (mi)",
     "gallons": "galon (gal)",
     "saveWorkshop": "Atölye Ayarlarını Kaydet",
-    "saved": "Atölye ayarları kaydedildi"
+    "saved": "Atölye ayarları kaydedildi",
+    "weekStartDay": "Hafta Baslangici",
+    "workDayStart": "Is Gunu Baslangici",
+    "workDayEnd": "Is Gunu Bitisi",
+    "weekDays": {
+      "0": "Pazar",
+      "1": "Pazartesi",
+      "2": "Sali",
+      "3": "Carsamba",
+      "4": "Persembe",
+      "5": "Cuma",
+      "6": "Cumartesi"
+    }
   },
   "maintenance": {
     "title": "Öngörülen Bakım",

+ 141 - 124
src/app/(authenticated)/settings/invoice/invoice-settings.tsx

@@ -1,46 +1,52 @@
-"use client";
+'use client'
 
-import { useState, useCallback } from "react";
-import { useRouter, useSearchParams } from "next/navigation";
-import { useTranslations } from "next-intl";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Button } from "@/components/ui/button";
-import { Separator } from "@/components/ui/separator";
-import { Textarea } from "@/components/ui/textarea";
-import { toast } from "sonner";
-import { setSettings } from "@/features/settings/Actions/settingsActions";
-import { SETTING_KEYS } from "@/features/settings/Schema/settingsSchema";
-import { FileText, Loader2, Save } from "lucide-react";
-import { ReadOnlyBanner, SaveButton, ReadOnlyWrapper } from "../read-only-guard";
-import { cn } from "@/lib/utils";
-import { InvoiceLayoutEditor } from "@/features/settings/Components/InvoiceLayoutEditor";
-import { InvoiceLayoutPreview } from "@/features/settings/Components/InvoiceLayoutPreview";
-import { saveInvoiceLayoutConfig, saveQuoteLayoutConfig } from "@/features/settings/Actions/invoiceLayoutActions";
-import { type InvoiceLayoutConfig, getDefaultInvoiceLayout } from "@/features/settings/Schema/invoiceLayoutSchema";
-import { CustomFieldsManager } from "@/features/custom-fields/Components/CustomFieldsManager";
+import { useState, useCallback } from 'react'
+import { useRouter, useSearchParams } from 'next/navigation'
+import { useTranslations } from 'next-intl'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { Button } from '@/components/ui/button'
+import { Separator } from '@/components/ui/separator'
+import { Textarea } from '@/components/ui/textarea'
+import { toast } from 'sonner'
+import { setSettings } from '@/features/settings/Actions/settingsActions'
+import { SETTING_KEYS } from '@/features/settings/Schema/settingsSchema'
+import { FileText, Loader2, Save } from 'lucide-react'
+import { ReadOnlyBanner, SaveButton, ReadOnlyWrapper } from '../read-only-guard'
+import { cn } from '@/lib/utils'
+import { InvoiceLayoutEditor } from '@/features/settings/Components/InvoiceLayoutEditor'
+import { InvoiceLayoutPreview } from '@/features/settings/Components/InvoiceLayoutPreview'
+import {
+  saveInvoiceLayoutConfig,
+  saveQuoteLayoutConfig,
+} from '@/features/settings/Actions/invoiceLayoutActions'
+import {
+  type InvoiceLayoutConfig,
+  getDefaultInvoiceLayout,
+} from '@/features/settings/Schema/invoiceLayoutSchema'
+import { CustomFieldsManager } from '@/features/custom-fields/Components/CustomFieldsManager'
 
-type TabType = "general" | "layout" | "customFields";
+type TabType = 'general' | 'layout' | 'customFields'
 
 interface FieldDef {
-  id: string;
-  name: string;
-  label: string;
-  fieldType: string;
-  entityType: string;
-  options: string | null;
-  required: boolean;
-  sortOrder: number;
-  isActive: boolean;
+  id: string
+  name: string
+  label: string
+  fieldType: string
+  entityType: string
+  options: string | null
+  required: boolean
+  sortOrder: number
+  isActive: boolean
 }
 
 interface InvoiceSettingsProps {
-  settings: Record<string, string>;
-  initialInvoiceLayout?: InvoiceLayoutConfig;
-  initialQuoteLayout?: InvoiceLayoutConfig;
-  customFields: FieldDef[];
-  customFieldsEnabled: boolean;
+  settings: Record<string, string>
+  initialInvoiceLayout?: InvoiceLayoutConfig
+  initialQuoteLayout?: InvoiceLayoutConfig
+  customFields: FieldDef[]
+  customFieldsEnabled: boolean
 }
 
 export function InvoiceSettings({
@@ -50,73 +56,82 @@ export function InvoiceSettings({
   customFields,
   customFieldsEnabled,
 }: InvoiceSettingsProps) {
-  const router = useRouter();
-  const searchParams = useSearchParams();
-  const t = useTranslations('settings');
-  const [saving, setSaving] = useState(false);
+  const router = useRouter()
+  const searchParams = useSearchParams()
+  const t = useTranslations('settings')
+  const [saving, setSaving] = useState(false)
 
-  const tab = (searchParams.get("tab") as TabType) || "general";
-  const setTab = useCallback((newTab: TabType) => {
-    const params = new URLSearchParams(searchParams.toString());
-    if (newTab === "general") {
-      params.delete("tab");
-    } else {
-      params.set("tab", newTab);
-    }
-    const qs = params.toString();
-    router.replace(`/settings/invoice${qs ? `?${qs}` : ""}`, { scroll: false });
-  }, [router, searchParams]);
+  const tab = (searchParams.get('tab') as TabType) || 'general'
+  const setTab = useCallback(
+    (newTab: TabType) => {
+      const params = new URLSearchParams(searchParams.toString())
+      if (newTab === 'general') {
+        params.delete('tab')
+      } else {
+        params.set('tab', newTab)
+      }
+      const qs = params.toString()
+      router.replace(`/settings/invoice${qs ? `?${qs}` : ''}`, { scroll: false })
+    },
+    [router, searchParams]
+  )
 
   // General tab state
-  const [invoicePrefix, setInvoicePrefix] = useState(settings[SETTING_KEYS.INVOICE_PREFIX] || "{year}-");
-  const [invoiceStartNumber, setInvoiceStartNumber] = useState(settings[SETTING_KEYS.INVOICE_START_NUMBER] || "");
-  const [dueDays, setDueDays] = useState(settings[SETTING_KEYS.INVOICE_DUE_DAYS] || "14");
-  const [footerNote, setFooterNote] = useState(settings[SETTING_KEYS.INVOICE_FOOTER_NOTE] || "");
+  const [invoicePrefix, setInvoicePrefix] = useState(
+    settings[SETTING_KEYS.INVOICE_PREFIX] || '{year}-'
+  )
+  const [invoiceStartNumber, setInvoiceStartNumber] = useState(
+    settings[SETTING_KEYS.INVOICE_START_NUMBER] || ''
+  )
+  const [dueDays, setDueDays] = useState(settings[SETTING_KEYS.INVOICE_DUE_DAYS] || '14')
+  const [footerNote, setFooterNote] = useState(settings[SETTING_KEYS.INVOICE_FOOTER_NOTE] || '')
 
   // Layout tab state
-  const [invoiceLayout, setInvoiceLayout] = useState(initialInvoiceLayout ?? getDefaultInvoiceLayout());
-  const [quoteLayout, setQuoteLayout] = useState(initialQuoteLayout ?? getDefaultInvoiceLayout());
-  const [layoutDocType, setLayoutDocType] = useState<"invoice" | "quote">("invoice");
+  const [invoiceLayout, setInvoiceLayout] = useState(
+    initialInvoiceLayout ?? getDefaultInvoiceLayout()
+  )
+  const [quoteLayout, setQuoteLayout] = useState(initialQuoteLayout ?? getDefaultInvoiceLayout())
+  const [layoutDocType, setLayoutDocType] = useState<'invoice' | 'quote'>('invoice')
 
   // Template values from saved settings
   const invoiceTemplate = {
-    primaryColor: settings[SETTING_KEYS.INVOICE_PRIMARY_COLOR] || "#d97706",
-    fontFamily: settings[SETTING_KEYS.INVOICE_FONT_FAMILY] || "Helvetica",
-    headerStyle: settings[SETTING_KEYS.INVOICE_HEADER_STYLE] || "standard",
-  };
+    primaryColor: settings[SETTING_KEYS.INVOICE_PRIMARY_COLOR] || '#d97706',
+    fontFamily: settings[SETTING_KEYS.INVOICE_FONT_FAMILY] || 'Helvetica',
+    headerStyle: settings[SETTING_KEYS.INVOICE_HEADER_STYLE] || 'standard',
+  }
   const quoteTemplate = {
     primaryColor: settings[SETTING_KEYS.QUOTE_PRIMARY_COLOR] || invoiceTemplate.primaryColor,
     fontFamily: settings[SETTING_KEYS.QUOTE_FONT_FAMILY] || invoiceTemplate.fontFamily,
     headerStyle: settings[SETTING_KEYS.QUOTE_HEADER_STYLE] || invoiceTemplate.headerStyle,
-  };
+  }
 
   const handleSaveGeneral = async () => {
-    setSaving(true);
+    setSaving(true)
     await setSettings({
       [SETTING_KEYS.INVOICE_PREFIX]: invoicePrefix,
       [SETTING_KEYS.INVOICE_START_NUMBER]: invoiceStartNumber,
       [SETTING_KEYS.INVOICE_DUE_DAYS]: dueDays,
       [SETTING_KEYS.INVOICE_FOOTER_NOTE]: footerNote,
-    });
-    setSaving(false);
-    router.refresh();
-    toast.success(t('invoice.saved'));
-  };
+    })
+    setSaving(false)
+    router.refresh()
+    toast.success(t('invoice.saved'))
+  }
 
   const handleSaveLayout = async () => {
-    setSaving(true);
+    setSaving(true)
     try {
-      if (layoutDocType === "invoice") {
-        await saveInvoiceLayoutConfig(invoiceLayout);
+      if (layoutDocType === 'invoice') {
+        await saveInvoiceLayoutConfig(invoiceLayout)
       } else {
-        await saveQuoteLayoutConfig(quoteLayout);
+        await saveQuoteLayoutConfig(quoteLayout)
       }
-      toast.success(t('invoice.layoutSaved'));
+      toast.success(t('invoice.layoutSaved'))
     } catch {
-      toast.error(t('templates.failedSave'));
+      toast.error(t('templates.failedSave'))
     }
-    setSaving(false);
-  };
+    setSaving(false)
+  }
 
   return (
     <div className="space-y-6">
@@ -124,9 +139,9 @@ export function InvoiceSettings({
       <div>
         <h2 className="text-lg font-semibold">{t('invoice.title')}</h2>
         <p className="text-sm text-muted-foreground">
-          {tab === "layout"
+          {tab === 'layout'
             ? t('invoice.layoutDescription')
-            : tab === "customFields"
+            : tab === 'customFields'
               ? t('customFields.description')
               : t('invoice.description')}
         </p>
@@ -136,24 +151,24 @@ export function InvoiceSettings({
       <div className="flex gap-1 rounded-lg border bg-muted p-1">
         <button
           type="button"
-          onClick={() => setTab("general")}
+          onClick={() => setTab('general')}
           className={cn(
-            "flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors",
-            tab === "general"
-              ? "bg-background text-foreground shadow-sm"
-              : "text-muted-foreground hover:text-foreground"
+            'flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors',
+            tab === 'general'
+              ? 'bg-background text-foreground shadow-sm'
+              : 'text-muted-foreground hover:text-foreground'
           )}
         >
           {t('invoice.tabs.general')}
         </button>
         <button
           type="button"
-          onClick={() => setTab("layout")}
+          onClick={() => setTab('layout')}
           className={cn(
-            "flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors",
-            tab === "layout"
-              ? "bg-background text-foreground shadow-sm"
-              : "text-muted-foreground hover:text-foreground"
+            'flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors',
+            tab === 'layout'
+              ? 'bg-background text-foreground shadow-sm'
+              : 'text-muted-foreground hover:text-foreground'
           )}
         >
           {t('invoice.tabs.layout')}
@@ -161,12 +176,12 @@ export function InvoiceSettings({
         {customFieldsEnabled && (
           <button
             type="button"
-            onClick={() => setTab("customFields")}
+            onClick={() => setTab('customFields')}
             className={cn(
-              "flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors",
-              tab === "customFields"
-                ? "bg-background text-foreground shadow-sm"
-                : "text-muted-foreground hover:text-foreground"
+              'flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors',
+              tab === 'customFields'
+                ? 'bg-background text-foreground shadow-sm'
+                : 'text-muted-foreground hover:text-foreground'
             )}
           >
             {t('invoice.tabs.customFields')}
@@ -174,7 +189,7 @@ export function InvoiceSettings({
         )}
       </div>
 
-      {tab === "general" ? (
+      {tab === 'general' ? (
         <ReadOnlyWrapper>
           <Card className="border-0 shadow-sm">
             <CardHeader className="flex flex-row items-center gap-3 pb-4">
@@ -196,7 +211,9 @@ export function InvoiceSettings({
                       code: (chunks) => <code className="rounded bg-muted px-1">{chunks}</code>,
                       bold: (chunks) => <span className="font-medium">{chunks}</span>,
                       year: '{year}',
-                      preview: invoicePrefix.replace(/\{year\}/g, String(new Date().getFullYear())) + (invoiceStartNumber || '1001'),
+                      preview:
+                        invoicePrefix.replace(/\{year\}/g, String(new Date().getFullYear())) +
+                        (invoiceStartNumber || '1001'),
                     })}
                   </p>
                 </div>
@@ -212,7 +229,9 @@ export function InvoiceSettings({
                     className="w-32"
                   />
                   <p className="text-xs text-muted-foreground">
-                    {t('invoice.nextInvoiceNumberHint', { example: invoicePrefix + (invoiceStartNumber || '...') })}
+                    {t('invoice.nextInvoiceNumberHint', {
+                      example: invoicePrefix + (invoiceStartNumber || '...'),
+                    })}
                   </p>
                 </div>
                 <div className="space-y-2">
@@ -226,9 +245,7 @@ export function InvoiceSettings({
                     onChange={(e) => setDueDays(e.target.value)}
                     className="w-24"
                   />
-                  <p className="text-xs text-muted-foreground">
-                    {t('invoice.dueDaysHint')}
-                  </p>
+                  <p className="text-xs text-muted-foreground">{t('invoice.dueDaysHint')}</p>
                 </div>
               </div>
 
@@ -243,9 +260,7 @@ export function InvoiceSettings({
                   value={footerNote}
                   onChange={(e) => setFooterNote(e.target.value)}
                 />
-                <p className="text-xs text-muted-foreground">
-                  {t('invoice.footerHint')}
-                </p>
+                <p className="text-xs text-muted-foreground">{t('invoice.footerHint')}</p>
               </div>
 
               <SaveButton>
@@ -264,7 +279,7 @@ export function InvoiceSettings({
             </CardContent>
           </Card>
         </ReadOnlyWrapper>
-      ) : tab === "layout" ? (
+      ) : tab === 'layout' ? (
         <>
           <ReadOnlyWrapper>
             <div className="space-y-4">
@@ -272,24 +287,24 @@ export function InvoiceSettings({
                 <div className="flex gap-1 rounded-lg border bg-muted p-1 max-w-xs">
                   <button
                     type="button"
-                    onClick={() => setLayoutDocType("invoice")}
+                    onClick={() => setLayoutDocType('invoice')}
                     className={cn(
-                      "flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
-                      layoutDocType === "invoice"
-                        ? "bg-background text-foreground shadow-sm"
-                        : "text-muted-foreground hover:text-foreground"
+                      'flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
+                      layoutDocType === 'invoice'
+                        ? 'bg-background text-foreground shadow-sm'
+                        : 'text-muted-foreground hover:text-foreground'
                     )}
                   >
                     {t('invoice.layoutDocInvoice')}
                   </button>
                   <button
                     type="button"
-                    onClick={() => setLayoutDocType("quote")}
+                    onClick={() => setLayoutDocType('quote')}
                     className={cn(
-                      "flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
-                      layoutDocType === "quote"
-                        ? "bg-background text-foreground shadow-sm"
-                        : "text-muted-foreground hover:text-foreground"
+                      'flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
+                      layoutDocType === 'quote'
+                        ? 'bg-background text-foreground shadow-sm'
+                        : 'text-muted-foreground hover:text-foreground'
                     )}
                   >
                     {t('invoice.layoutDocQuote')}
@@ -300,10 +315,10 @@ export function InvoiceSettings({
                     variant="outline"
                     size="sm"
                     onClick={() => {
-                      if (layoutDocType === "invoice") {
-                        setInvoiceLayout(getDefaultInvoiceLayout());
+                      if (layoutDocType === 'invoice') {
+                        setInvoiceLayout(getDefaultInvoiceLayout())
                       } else {
-                        setQuoteLayout(getDefaultInvoiceLayout());
+                        setQuoteLayout(getDefaultInvoiceLayout())
                       }
                     }}
                   >
@@ -317,19 +332,21 @@ export function InvoiceSettings({
               </div>
               <div className="grid gap-6 xl:grid-cols-2">
                 <InvoiceLayoutEditor
-                  config={layoutDocType === "invoice" ? invoiceLayout : quoteLayout}
-                  onChange={layoutDocType === "invoice" ? setInvoiceLayout : setQuoteLayout}
+                  config={layoutDocType === 'invoice' ? invoiceLayout : quoteLayout}
+                  onChange={layoutDocType === 'invoice' ? setInvoiceLayout : setQuoteLayout}
                   documentType={layoutDocType}
                   customFields={customFields}
                 />
                 <div className="hidden xl:block pr-2">
                   <div className="sticky top-6 space-y-2">
-                    <p className="text-xs font-medium text-muted-foreground">{t('invoice.preview')}</p>
+                    <p className="text-xs font-medium text-muted-foreground">
+                      {t('invoice.preview')}
+                    </p>
                     <InvoiceLayoutPreview
-                      config={layoutDocType === "invoice" ? invoiceLayout : quoteLayout}
+                      config={layoutDocType === 'invoice' ? invoiceLayout : quoteLayout}
                       documentType={layoutDocType}
                       customFields={customFields}
-                      template={layoutDocType === "invoice" ? invoiceTemplate : quoteTemplate}
+                      template={layoutDocType === 'invoice' ? invoiceTemplate : quoteTemplate}
                       logoUrl={settings[SETTING_KEYS.COMPANY_LOGO] || undefined}
                     />
                   </div>
@@ -349,9 +366,9 @@ export function InvoiceSettings({
       ) : (
         <CustomFieldsManager
           initialFields={customFields}
-          layoutConfig={layoutDocType === "invoice" ? invoiceLayout : quoteLayout}
+          layoutConfig={layoutDocType === 'invoice' ? invoiceLayout : quoteLayout}
         />
       )}
     </div>
-  );
+  )
 }

+ 35 - 61
src/app/(authenticated)/settings/workshop/workshop-settings.tsx

@@ -21,7 +21,7 @@ import {
 import { toast } from "sonner";
 import { setSettings } from "@/features/settings/Actions/settingsActions";
 import { SETTING_KEYS } from "@/features/settings/Schema/settingsSchema";
-import { Loader2, Ruler, Save, Wrench, CalendarDays, Check, ChevronsUpDown, Plus } from "lucide-react";
+import { Loader2, Ruler, Save, Wrench, Check, ChevronsUpDown, Plus } from "lucide-react";
 import {
   Select,
   SelectContent,
@@ -55,9 +55,6 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
   const [defaultLaborRate, setDefaultLaborRate] = useState(
     settings[SETTING_KEYS.DEFAULT_LABOR_RATE] || ""
   );
-  const [workingHours, setWorkingHours] = useState(
-    settings[SETTING_KEYS.WORKING_HOURS] || ""
-  );
   const [unitSystem, setUnitSystem] = useState(
     settings[SETTING_KEYS.UNIT_SYSTEM] || "imperial"
   );
@@ -101,7 +98,6 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
     await setSettings({
       [SETTING_KEYS.DEFAULT_TECHNICIAN]: defaultTechnician,
       [SETTING_KEYS.DEFAULT_LABOR_RATE]: defaultLaborRate,
-      [SETTING_KEYS.WORKING_HOURS]: workingHours,
       [SETTING_KEYS.UNIT_SYSTEM]: unitSystem,
       [SETTING_KEYS.WORKBOARD_WEEK_START_DAY]: weekStartDay,
       [SETTING_KEYS.WORKBOARD_WORK_DAY_START]: workDayStart,
@@ -240,14 +236,40 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
             </div>
           </div>
 
-          <div className="space-y-2">
-            <Label htmlFor="workingHours">{t('workshop.workingHours')}</Label>
-            <Input
-              id="workingHours"
-              placeholder={t('workshop.workingHoursPlaceholder')}
-              value={workingHours}
-              onChange={(e) => setWorkingHours(e.target.value)}
-            />
+          <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="space-y-2">
+              <Label htmlFor="workDayStart">{t('workshop.workDayStart')}</Label>
+              <Input
+                id="workDayStart"
+                type="time"
+                value={workDayStart}
+                onChange={(e) => setWorkDayStart(e.target.value)}
+              />
+            </div>
+            <div className="space-y-2">
+              <Label htmlFor="workDayEnd">{t('workshop.workDayEnd')}</Label>
+              <Input
+                id="workDayEnd"
+                type="time"
+                value={workDayEnd}
+                onChange={(e) => setWorkDayEnd(e.target.value)}
+              />
+            </div>
           </div>
 
           <Separator />
@@ -289,54 +311,6 @@ export function WorkshopSettings({ settings, technicians: initialTechnicians = [
             </div>
           </div>
 
-          <Separator />
-
-          <div className="space-y-4">
-            <div className="flex flex-row items-center gap-3">
-              <CalendarDays className="h-5 w-5 text-muted-foreground" />
-              <h3 className="text-lg font-semibold">{t('workshop.workBoardTitle')}</h3>
-            </div>
-            <p className="text-sm text-muted-foreground">
-              {t('workshop.workBoardDescription')}
-            </p>
-
-            <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="space-y-2">
-                <Label htmlFor="workDayStart">{t('workshop.workDayStart')}</Label>
-                <Input
-                  id="workDayStart"
-                  type="time"
-                  value={workDayStart}
-                  onChange={(e) => setWorkDayStart(e.target.value)}
-                />
-              </div>
-              <div className="space-y-2">
-                <Label htmlFor="workDayEnd">{t('workshop.workDayEnd')}</Label>
-                <Input
-                  id="workDayEnd"
-                  type="time"
-                  value={workDayEnd}
-                  onChange={(e) => setWorkDayEnd(e.target.value)}
-                />
-              </div>
-            </div>
-          </div>
-
           <SaveButton>
             <Separator />
             <div className="flex items-center gap-3">

+ 13 - 4
src/app/(authenticated)/vehicles/vehicles-client.tsx

@@ -34,6 +34,7 @@ import {
   Archive,
   ArchiveRestore,
   Gauge,
+  Grid3X3,
   LayoutGrid,
   List,
   Loader2,
@@ -233,13 +234,21 @@ export function VehiclesClient({
               <List className="h-4 w-4" />
             </Button>
             <Button
-              variant={view === 'grid' || view === 'grid6' ? 'secondary' : 'ghost'}
+              variant={view === 'grid' ? 'secondary' : 'ghost'}
               size="icon"
-              className="h-8 w-8 rounded-l-none"
+              className="h-8 w-8 rounded-l-none lg:rounded-none border-l lg:border-x"
               onClick={() => toggleView('grid')}
             >
               <LayoutGrid className="h-4 w-4" />
             </Button>
+            <Button
+              variant={view === 'grid6' ? 'secondary' : 'ghost'}
+              size="icon"
+              className="h-8 w-8 rounded-l-none hidden lg:inline-flex"
+              onClick={() => toggleView('grid6')}
+            >
+              <Grid3X3 className="h-4 w-4" />
+            </Button>
           </div>
           {!isArchived && (
             <Button size="sm" className="ml-auto" onClick={() => setShowForm(true)}>
@@ -285,7 +294,7 @@ export function VehiclesClient({
                     {v.customer?.name || '-'}
                   </TableCell>
                   <TableCell className="hidden md:table-cell text-right font-mono text-sm">
-                    {v.mileage.toLocaleString()}
+                    {new Intl.NumberFormat('en-US').format(v.mileage)}
                   </TableCell>
                   <TableCell className="text-center">{v._count.serviceRecords}</TableCell>
                   <TableCell>
@@ -445,7 +454,7 @@ export function VehiclesClient({
                 <CardContent className={`flex items-center justify-between ${view === 'grid6' ? 'p-2 text-xs' : 'p-4 text-sm'}`}>
                   <div className="flex items-center gap-1.5 text-muted-foreground">
                     <Gauge className={view === 'grid6' ? 'h-3 w-3' : 'h-3.5 w-3.5'} />
-                    <span className="font-medium">{v.mileage.toLocaleString()}</span>
+                    <span className="font-medium">{new Intl.NumberFormat('en-US').format(v.mileage)}</span>
                   </div>
                   <div className={`flex items-center ${view === 'grid6' ? 'gap-2' : 'gap-3'} text-muted-foreground`}>
                     {v.customer && <span className={view === 'grid6' ? 'text-[10px] hidden xl:inline' : 'text-xs'}>{v.customer.name}</span>}

+ 79 - 84
src/app/(public)/share/invoice/[orgId]/[token]/invoice-view.tsx

@@ -243,13 +243,11 @@ export function InvoiceView({
   const balanceDue = displayTotal - totalPaid
   const shopName = workshop.name || record.shopName || 'Torqvoice'
 
-  // Layout config overrides for header field visibility
-  const effectiveShowLogo = layoutConfig
-    ? isFieldVisible(layoutConfig, 'header', 'logo')
-    : showLogo
-  const effectiveShowCompanyName = layoutConfig
-    ? isFieldVisible(layoutConfig, 'header', 'company_name')
-    : showCompanyName
+  // Layout config overrides for header field visibility & ordering
+  const headerVisibleFields = getVisibleFieldsForSection(layoutConfig, 'header')
+  const headerFieldOrder = getOrderedFieldIds(headerVisibleFields, ['logo', 'company_name', 'company_address', 'company_phone', 'company_email', 'company_org_number'])
+  const effectiveShowLogo = headerVisibleFields ? headerFieldOrder.includes('logo') : showLogo
+  const effectiveShowCompanyName = headerVisibleFields ? headerFieldOrder.includes('company_name') : showCompanyName
 
   const sectionOrder = getSectionOrder(layoutConfig)
 
@@ -596,90 +594,87 @@ export function InvoiceView({
             case 'header':
               return (
                 <div key="header">
-                  {headerStyle === 'modern' ? (
-                    <>
-                      <div className="rounded-lg p-6 text-center text-white" style={{ backgroundColor: primaryColor }}>
-                        {effectiveShowLogo && logoUrl && (
-                          <img
-                            src={logoUrl}
-                            alt={shopName}
-                            className="mx-auto mb-2 object-contain"
-                            style={{ maxHeight: 64 * (logoSize / 100), maxWidth: 180 * (logoSize / 100) }}
-                          />
-                        )}
-                        {effectiveShowCompanyName && (
-                          <h2 className="text-xl font-bold sm:text-2xl">{shopName}</h2>
-                        )}
-                        {workshop.address && <p className="mt-1 text-sm opacity-80">{workshop.address}</p>}
-                        <div className="mt-1 flex flex-wrap justify-center gap-3 text-sm opacity-70">
-                          {workshop.phone && <span>{t('tel', { phone: workshop.phone })}</span>}
-                          {workshop.email && <span>{workshop.email}</span>}
+                  {(() => {
+                    const renderModernField = (fid: string) => {
+                      switch (fid) {
+                        case 'logo': return effectiveShowLogo && logoUrl ? <img key="logo" src={logoUrl} alt={shopName} className="mx-auto mb-2 object-contain" style={{ maxHeight: 64 * (logoSize / 100), maxWidth: 180 * (logoSize / 100) }} /> : null
+                        case 'company_name': return effectiveShowCompanyName ? <h2 key="cn" className="text-xl font-bold sm:text-2xl">{shopName}</h2> : null
+                        case 'company_address': return workshop.address ? <p key="addr" className="mt-1 text-sm opacity-80">{workshop.address}</p> : null
+                        case 'company_phone': return workshop.phone ? <p key="ph" className="text-sm opacity-70">{t('tel', { phone: workshop.phone })}</p> : null
+                        case 'company_email': return workshop.email ? <p key="em" className="text-sm opacity-70">{workshop.email}</p> : null
+                        case 'company_org_number': return null
+                        default: return null
+                      }
+                    }
+                    const renderCompactField = (fid: string) => {
+                      switch (fid) {
+                        case 'logo': return effectiveShowLogo && logoUrl ? <img key="logo" src={logoUrl} alt={shopName} className="rounded object-contain" style={{ height: 48 * (logoSize / 100), width: 48 * (logoSize / 100) }} /> : null
+                        case 'company_name': return effectiveShowCompanyName ? <h2 key="cn" className="text-lg font-bold" style={{ color: primaryColor }}>{shopName}</h2> : null
+                        case 'company_address': return workshop.address ? <p key="addr" className="text-sm text-gray-500">{workshop.address}</p> : null
+                        case 'company_phone': return workshop.phone ? <p key="ph" className="text-sm text-gray-500">{t('tel', { phone: workshop.phone })}</p> : null
+                        case 'company_email': return workshop.email ? <p key="em" className="text-sm text-gray-500">{workshop.email}</p> : null
+                        case 'company_org_number': return null
+                        default: return null
+                      }
+                    }
+                    const renderStandardField = (fid: string) => {
+                      switch (fid) {
+                        case 'logo': return effectiveShowLogo && logoUrl ? <img key="logo" src={logoUrl} alt={shopName} className="mb-2 object-contain object-left" style={{ maxHeight: 64 * (logoSize / 100), maxWidth: 180 * (logoSize / 100) }} /> : null
+                        case 'company_name': return effectiveShowCompanyName ? <h2 key="cn" className="text-xl font-bold sm:text-2xl" style={{ color: primaryColor }}>{shopName}</h2> : null
+                        case 'company_address': return workshop.address ? <p key="addr" className="mt-1 text-sm text-gray-500">{workshop.address}</p> : null
+                        case 'company_phone': return workshop.phone ? <p key="ph" className="text-sm text-gray-500">{t('tel', { phone: workshop.phone })}</p> : null
+                        case 'company_email': return workshop.email ? <p key="em" className="text-sm text-gray-500">{workshop.email}</p> : null
+                        case 'company_org_number': return null
+                        default: return null
+                      }
+                    }
+
+                    if (headerStyle === 'modern') return (
+                      <>
+                        <div className="rounded-lg p-6 text-center text-white" style={{ backgroundColor: primaryColor }}>
+                          {headerFieldOrder.map(renderModernField)}
                         </div>
-                      </div>
-                      <div className="mt-4 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
-                        <h3 className="text-xl font-bold uppercase">{t('title')}</h3>
-                        <div className="flex gap-3 text-sm text-gray-500">
-                          <span>{invoiceNum}</span>
-                          <span>{serviceDate}</span>
+                        <div className="mt-4 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
+                          <h3 className="text-xl font-bold uppercase">{t('title')}</h3>
+                          <div className="flex gap-3 text-sm text-gray-500">
+                            <span>{invoiceNum}</span>
+                            <span>{serviceDate}</span>
+                          </div>
                         </div>
-                      </div>
-                    </>
-                  ) : headerStyle === 'compact' ? (
-                    <div className="flex flex-col gap-4 border-b pb-6 sm:flex-row sm:items-center sm:justify-between" style={{ borderColor: '#e5e7eb' }}>
-                      <div className="flex items-center gap-3">
-                        {effectiveShowLogo && logoUrl && (
-                          <img
-                            src={logoUrl}
-                            alt={shopName}
-                            className="rounded object-contain"
-                            style={{ height: 48 * (logoSize / 100), width: 48 * (logoSize / 100) }}
-                          />
-                        )}
+                      </>
+                    )
+                    if (headerStyle === 'compact') return (
+                      <div className="flex flex-col gap-4 border-b pb-6 sm:flex-row sm:items-start sm:justify-between" style={{ borderColor: '#e5e7eb' }}>
                         <div>
-                          {effectiveShowCompanyName && (
-                            <h2 className="text-lg font-bold" style={{ color: primaryColor }}>{shopName}</h2>
-                          )}
-                          {workshop.address && <p className="text-sm text-gray-500">{workshop.address}</p>}
+                          {headerFieldOrder.map(renderCompactField)}
+                        </div>
+                        <div className="sm:text-right">
+                          <h3 className="text-lg font-bold uppercase">{t('title')}</h3>
+                          <p className="text-sm text-gray-500">{invoiceNum}</p>
+                          <p className="text-sm text-gray-500">{serviceDate}</p>
                         </div>
                       </div>
-                      <div className="sm:text-right">
-                        <h3 className="text-lg font-bold uppercase">{t('title')}</h3>
-                        <p className="text-sm text-gray-500">{invoiceNum}</p>
-                        <p className="text-sm text-gray-500">{serviceDate}</p>
-                      </div>
-                    </div>
-                  ) : (
+                    )
                     /* Standard */
-                    <div className="flex flex-col gap-4 border-b-2 pb-6 sm:flex-row sm:items-start sm:justify-between" style={{ borderColor: primaryColor }}>
-                      <div>
-                        {effectiveShowLogo && logoUrl && (
-                          <img
-                            src={logoUrl}
-                            alt={shopName}
-                            className="mb-2 object-contain object-left"
-                            style={{ maxHeight: 64 * (logoSize / 100), maxWidth: 180 * (logoSize / 100) }}
-                          />
-                        )}
-                        {effectiveShowCompanyName && (
-                          <h2 className="text-xl font-bold sm:text-2xl" style={{ color: primaryColor }}>{shopName}</h2>
-                        )}
-                        {workshop.address && <p className="mt-1 text-sm text-gray-500">{workshop.address}</p>}
-                        {workshop.phone && <p className="text-sm text-gray-500">{t('tel', { phone: workshop.phone })}</p>}
-                        {workshop.email && <p className="text-sm text-gray-500">{workshop.email}</p>}
-                      </div>
-                      <div className="sm:text-right">
-                        {showTorqvoiceBranding && (
-                          <div className="mb-2 inline-flex items-center gap-1.5 rounded-full bg-gray-100 px-2.5 py-1 dark:bg-gray-800">
-                            <img src="/torqvoice_app_logo.png" alt="Torqvoice" className="h-4 w-4" />
-                            <span className="text-xs font-semibold text-gray-500 dark:text-gray-400">Torqvoice</span>
-                          </div>
-                        )}
-                        <h3 className="text-xl font-bold uppercase" style={{ color: primaryColor }}>{t('title')}</h3>
-                        <p className="mt-1 text-sm text-gray-500">{invoiceNum}</p>
-                        <p className="text-sm text-gray-500">{serviceDate}</p>
+                    return (
+                      <div className="flex flex-col gap-4 border-b-2 pb-6 sm:flex-row sm:items-start sm:justify-between" style={{ borderColor: primaryColor }}>
+                        <div>
+                          {headerFieldOrder.map(renderStandardField)}
+                        </div>
+                        <div className="sm:text-right">
+                          {showTorqvoiceBranding && (
+                            <div className="mb-2 inline-flex items-center gap-1.5 rounded-full bg-gray-100 px-2.5 py-1 dark:bg-gray-800">
+                              <img src="/torqvoice_app_logo.png" alt="Torqvoice" className="h-4 w-4" />
+                              <span className="text-xs font-semibold text-gray-500 dark:text-gray-400">Torqvoice</span>
+                            </div>
+                          )}
+                          <h3 className="text-xl font-bold uppercase" style={{ color: primaryColor }}>{t('title')}</h3>
+                          <p className="mt-1 text-sm text-gray-500">{invoiceNum}</p>
+                          <p className="text-sm text-gray-500">{serviceDate}</p>
+                        </div>
                       </div>
-                    </div>
-                  )}
+                    )
+                  })()}
                 </div>
               )
 

+ 100 - 125
src/app/(public)/share/quote/[orgId]/[token]/quote-view.tsx

@@ -208,13 +208,11 @@ export function QuoteView({
   const validUntilDate = quote.validUntil ? fmtDate(quote.validUntil, df, tz) : null;
   const shopName = workshop.name || "Torqvoice";
 
-  // Layout config overrides for header field visibility
-  const effectiveShowLogo = layoutConfig
-    ? isFieldVisible(layoutConfig, 'header', 'logo')
-    : true;
-  const effectiveShowCompanyName = layoutConfig
-    ? isFieldVisible(layoutConfig, 'header', 'company_name')
-    : true;
+  // Layout config overrides for header field visibility & ordering
+  const headerVisibleFields = getVisibleFieldsForSection(layoutConfig, 'header');
+  const headerFieldOrder = getOrderedFieldIds(headerVisibleFields, ['logo', 'company_name', 'company_address', 'company_phone', 'company_email', 'company_org_number']);
+  const effectiveShowLogo = headerVisibleFields ? headerFieldOrder.includes('logo') : true;
+  const effectiveShowCompanyName = headerVisibleFields ? headerFieldOrder.includes('company_name') : true;
 
   const sectionOrder = getSectionOrder(layoutConfig);
 
@@ -335,132 +333,109 @@ export function QuoteView({
             case 'header':
               return (
                 <div key="header">
-                  {headerStyle === "modern" ? (
-                    <>
-                      <div className="rounded-lg p-6 text-center text-white" style={{ backgroundColor: primaryColor }}>
-                        {effectiveShowLogo && (logoUrl || "/torqvoice_app_logo.png") && (
-                          <img
-                            src={logoUrl || "/torqvoice_app_logo.png"}
-                            alt={shopName}
-                            className="mx-auto mb-2 object-contain"
-                            style={{ maxHeight: 64 * (logoSize / 100), maxWidth: 180 * (logoSize / 100) }}
-                          />
-                        )}
-                        {effectiveShowCompanyName && (
-                          <h2 className="text-xl font-bold sm:text-2xl">{shopName}</h2>
-                        )}
-                        {workshop.address && <p className="mt-1 text-sm opacity-80">{workshop.address}</p>}
-                        <div className="mt-1 flex flex-wrap justify-center gap-3 text-sm opacity-70">
-                          {workshop.phone && <span>{t('tel', { phone: workshop.phone })}</span>}
-                          {workshop.email && <span>{workshop.email}</span>}
-                        </div>
-                      </div>
-                      <div className="mt-4 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
-                        <div className="flex items-center gap-3">
-                          <h3 className="text-xl font-bold">{t('title').toUpperCase()}</h3>
-                          <span className={`inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${
-                            status === "accepted"
-                              ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
-                              : status === "rejected"
-                              ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
-                              : status === "changes_requested"
-                              ? "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
-                              : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
-                          }`}>
-                            {statusLabels[status] || status}
-                          </span>
+                  {(() => {
+                    const statusBadge = (extraClass?: string) => (
+                      <span className={`${extraClass || ''} inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${
+                        status === "accepted"
+                          ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
+                          : status === "rejected"
+                          ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
+                          : status === "changes_requested"
+                          ? "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
+                          : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
+                      }`}>
+                        {statusLabels[status] || status}
+                      </span>
+                    )
+                    const renderModernField = (fid: string) => {
+                      switch (fid) {
+                        case 'logo': return effectiveShowLogo ? <img key="logo" src={logoUrl || "/torqvoice_app_logo.png"} alt={shopName} className="mx-auto mb-2 object-contain" style={{ maxHeight: 64 * (logoSize / 100), maxWidth: 180 * (logoSize / 100) }} /> : null
+                        case 'company_name': return effectiveShowCompanyName ? <h2 key="cn" className="text-xl font-bold sm:text-2xl">{shopName}</h2> : null
+                        case 'company_address': return workshop.address ? <p key="addr" className="mt-1 text-sm opacity-80">{workshop.address}</p> : null
+                        case 'company_phone': return workshop.phone ? <p key="ph" className="text-sm opacity-70">{t('tel', { phone: workshop.phone })}</p> : null
+                        case 'company_email': return workshop.email ? <p key="em" className="text-sm opacity-70">{workshop.email}</p> : null
+                        default: return null
+                      }
+                    }
+                    const renderCompactField = (fid: string) => {
+                      switch (fid) {
+                        case 'logo': return effectiveShowLogo ? <img key="logo" src={logoUrl || "/torqvoice_app_logo.png"} alt={shopName} className="rounded object-contain" style={{ height: 48 * (logoSize / 100), width: 48 * (logoSize / 100) }} /> : null
+                        case 'company_name': return effectiveShowCompanyName ? <h2 key="cn" className="text-lg font-bold" style={{ color: primaryColor }}>{shopName}</h2> : null
+                        case 'company_address': return workshop.address ? <p key="addr" className="text-sm text-gray-500">{workshop.address}</p> : null
+                        case 'company_phone': return workshop.phone ? <p key="ph" className="text-sm text-gray-500">{t('tel', { phone: workshop.phone })}</p> : null
+                        case 'company_email': return workshop.email ? <p key="em" className="text-sm text-gray-500">{workshop.email}</p> : null
+                        default: return null
+                      }
+                    }
+                    const renderStandardField = (fid: string) => {
+                      switch (fid) {
+                        case 'logo': return effectiveShowLogo ? <img key="logo" src={logoUrl || "/torqvoice_app_logo.png"} alt={shopName} className="mb-2 object-contain object-left" style={{ maxHeight: 64 * (logoSize / 100), maxWidth: 180 * (logoSize / 100) }} /> : null
+                        case 'company_name': return effectiveShowCompanyName ? <h2 key="cn" className="text-xl font-bold sm:text-2xl" style={{ color: primaryColor }}>{shopName}</h2> : null
+                        case 'company_address': return workshop.address ? <p key="addr" className="mt-1 text-sm text-gray-500">{workshop.address}</p> : null
+                        case 'company_phone': return workshop.phone ? <p key="ph" className="text-sm text-gray-500">{t('tel', { phone: workshop.phone })}</p> : null
+                        case 'company_email': return workshop.email ? <p key="em" className="text-sm text-gray-500">{workshop.email}</p> : null
+                        default: return null
+                      }
+                    }
+
+                    if (headerStyle === "modern") return (
+                      <>
+                        <div className="rounded-lg p-6 text-center text-white" style={{ backgroundColor: primaryColor }}>
+                          {headerFieldOrder.map(renderModernField)}
                         </div>
-                        <div className="flex gap-3 text-sm text-gray-500">
-                          <span>{quoteNum}</span>
-                          <span>{createdDate}</span>
-                          {validUntilDate && <span>{t('validUntil', { date: validUntilDate })}</span>}
+                        <div className="mt-4 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
+                          <div className="flex items-center gap-3">
+                            <h3 className="text-xl font-bold">{t('title').toUpperCase()}</h3>
+                            {statusBadge()}
+                          </div>
+                          <div className="flex gap-3 text-sm text-gray-500">
+                            <span>{quoteNum}</span>
+                            <span>{createdDate}</span>
+                            {validUntilDate && <span>{t('validUntil', { date: validUntilDate })}</span>}
+                          </div>
                         </div>
-                      </div>
-                    </>
-                  ) : headerStyle === "compact" ? (
-                    <div className="flex flex-col gap-4 border-b pb-6 sm:flex-row sm:items-center sm:justify-between" style={{ borderColor: "#e5e7eb" }}>
-                      <div className="flex items-center gap-3">
-                        {effectiveShowLogo && (
-                          <img
-                            src={logoUrl || "/torqvoice_app_logo.png"}
-                            alt={shopName}
-                            className="rounded object-contain"
-                            style={{ height: 48 * (logoSize / 100), width: 48 * (logoSize / 100) }}
-                          />
-                        )}
+                      </>
+                    )
+                    if (headerStyle === "compact") return (
+                      <div className="flex flex-col gap-4 border-b pb-6 sm:flex-row sm:items-start sm:justify-between" style={{ borderColor: "#e5e7eb" }}>
                         <div>
-                          {effectiveShowCompanyName && (
-                            <h2 className="text-lg font-bold" style={{ color: primaryColor }}>{shopName}</h2>
+                          {headerFieldOrder.map(renderCompactField)}
+                        </div>
+                        <div className="sm:text-right">
+                          <h3 className="text-lg font-bold">{t('title').toUpperCase()}</h3>
+                          <p className="text-sm text-gray-500">{quoteNum}</p>
+                          <p className="text-sm text-gray-500">{createdDate}</p>
+                          {validUntilDate && (
+                            <p className="text-sm text-gray-500">{t('validUntil', { date: validUntilDate })}</p>
                           )}
-                          {workshop.address && <p className="text-sm text-gray-500">{workshop.address}</p>}
+                          {statusBadge("mt-1")}
                         </div>
                       </div>
-                      <div className="sm:text-right">
-                        <h3 className="text-lg font-bold">{t('title').toUpperCase()}</h3>
-                        <p className="text-sm text-gray-500">{quoteNum}</p>
-                        <p className="text-sm text-gray-500">{createdDate}</p>
-                        {validUntilDate && (
-                          <p className="text-sm text-gray-500">{t('validUntil', { date: validUntilDate })}</p>
-                        )}
-                        <span className={`mt-1 inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${
-                          status === "accepted"
-                            ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
-                            : status === "rejected"
-                            ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
-                            : status === "changes_requested"
-                            ? "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
-                            : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
-                        }`}>
-                          {statusLabels[status] || status}
-                        </span>
-                      </div>
-                    </div>
-                  ) : (
+                    )
                     /* Standard */
-                    <div className="flex flex-col gap-4 border-b-2 pb-6 sm:flex-row sm:items-start sm:justify-between" style={{ borderColor: primaryColor }}>
-                      <div>
-                        {effectiveShowLogo && (
-                          <img
-                            src={logoUrl || "/torqvoice_app_logo.png"}
-                            alt={shopName}
-                            className="mb-2 object-contain object-left"
-                            style={{ maxHeight: 64 * (logoSize / 100), maxWidth: 180 * (logoSize / 100) }}
-                          />
-                        )}
-                        {effectiveShowCompanyName && (
-                          <h2 className="text-xl font-bold sm:text-2xl" style={{ color: primaryColor }}>{shopName}</h2>
-                        )}
-                        {workshop.address && <p className="mt-1 text-sm text-gray-500">{workshop.address}</p>}
-                        {workshop.phone && <p className="text-sm text-gray-500">{t('tel', { phone: workshop.phone })}</p>}
-                        {workshop.email && <p className="text-sm text-gray-500">{workshop.email}</p>}
-                      </div>
-                      <div className="sm:text-right">
-                        {showTorqvoiceBranding && (
-                          <div className="mb-2 inline-flex items-center gap-1.5 rounded-full bg-gray-100 px-2.5 py-1 dark:bg-gray-800">
-                            <img src="/torqvoice_app_logo.png" alt="Torqvoice" className="h-4 w-4" />
-                            <span className="text-xs font-semibold text-gray-500 dark:text-gray-400">Torqvoice</span>
-                          </div>
-                        )}
-                        <h3 className="text-xl font-bold" style={{ color: primaryColor }}>{t('title').toUpperCase()}</h3>
-                        <p className="mt-1 text-sm text-gray-500">{quoteNum}</p>
-                        <p className="text-sm text-gray-500">{createdDate}</p>
-                        {validUntilDate && (
-                          <p className="text-sm text-gray-500">{t('validUntil', { date: validUntilDate })}</p>
-                        )}
-                        <span className={`mt-2 inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${
-                          status === "accepted"
-                            ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
-                            : status === "rejected"
-                            ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
-                            : status === "changes_requested"
-                            ? "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
-                            : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
-                        }`}>
-                          {statusLabels[status] || status}
-                        </span>
+                    return (
+                      <div className="flex flex-col gap-4 border-b-2 pb-6 sm:flex-row sm:items-start sm:justify-between" style={{ borderColor: primaryColor }}>
+                        <div>
+                          {headerFieldOrder.map(renderStandardField)}
+                        </div>
+                        <div className="sm:text-right">
+                          {showTorqvoiceBranding && (
+                            <div className="mb-2 inline-flex items-center gap-1.5 rounded-full bg-gray-100 px-2.5 py-1 dark:bg-gray-800">
+                              <img src="/torqvoice_app_logo.png" alt="Torqvoice" className="h-4 w-4" />
+                              <span className="text-xs font-semibold text-gray-500 dark:text-gray-400">Torqvoice</span>
+                            </div>
+                          )}
+                          <h3 className="text-xl font-bold" style={{ color: primaryColor }}>{t('title').toUpperCase()}</h3>
+                          <p className="mt-1 text-sm text-gray-500">{quoteNum}</p>
+                          <p className="text-sm text-gray-500">{createdDate}</p>
+                          {validUntilDate && (
+                            <p className="text-sm text-gray-500">{t('validUntil', { date: validUntilDate })}</p>
+                          )}
+                          {statusBadge("mt-2")}
+                        </div>
                       </div>
-                    </div>
-                  )}
+                    )
+                  })()}
                 </div>
               );
 

+ 16 - 9
src/features/admin/Components/admin-overview.tsx

@@ -1,5 +1,6 @@
 "use client";
 
+import Link from "next/link";
 import {
   Card,
   CardContent,
@@ -24,36 +25,42 @@ export function AdminOverview({ stats }: { stats: AdminStats }) {
       title: t("overview.totalUsers"),
       value: stats.totalUsers,
       icon: Users,
+      href: "/admin/users",
     },
     {
       title: t("overview.organizations"),
       value: stats.totalOrganizations,
       icon: Building2,
+      href: "/admin/organizations",
     },
     {
       title: t("overview.activeSubscriptions"),
       value: stats.totalActiveSubscriptions,
       icon: CreditCard,
+      href: "/admin/organizations",
     },
     {
       title: t("overview.monthlyRevenue"),
       value: `$${stats.totalRevenue.toFixed(2)}`,
       icon: DollarSign,
+      href: "/admin/organizations",
     },
   ];
 
   return (
     <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
       {cards.map((card) => (
-        <Card key={card.title}>
-          <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-            <CardTitle className="text-sm font-medium">{card.title}</CardTitle>
-            <card.icon className="h-4 w-4 text-muted-foreground" />
-          </CardHeader>
-          <CardContent>
-            <div className="text-2xl font-bold">{card.value}</div>
-          </CardContent>
-        </Card>
+        <Link key={card.title} href={card.href}>
+          <Card className="transition-colors hover:bg-muted/50 cursor-pointer">
+            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+              <CardTitle className="text-sm font-medium">{card.title}</CardTitle>
+              <card.icon className="h-4 w-4 text-muted-foreground" />
+            </CardHeader>
+            <CardContent>
+              <div className="text-2xl font-bold">{card.value}</div>
+            </CardContent>
+          </Card>
+        </Link>
       ))}
     </div>
   );

+ 87 - 83
src/features/quotes/Components/QuotePDF.tsx

@@ -6,7 +6,9 @@ import { HtmlToPdf } from '@/features/vehicles/Components/invoice-pdf/Notes'
 import { CustomFields } from '@/features/vehicles/Components/invoice-pdf/CustomFields'
 import type { TemplateConfig } from '@/features/vehicles/Components/invoice-pdf/types'
 import type { InvoiceLayoutConfig, InvoiceSection } from '@/features/settings/Schema/invoiceLayoutSchema'
-import { isCustomFieldId, fromCustomFieldId, groupSectionsForRendering, getDefaultInvoiceLayout, getOrderedFieldIds } from '@/features/settings/Schema/invoiceLayoutSchema'
+import { isCustomFieldId, fromCustomFieldId, groupSectionsForRendering, getDefaultInvoiceLayout, getOrderedFieldIds, getVisibleFieldsForSection as getVisibleFieldsForSectionHelper } from '@/features/settings/Schema/invoiceLayoutSchema'
+
+const DEFAULT_HEADER_FIELD_ORDER = ['logo', 'company_name', 'company_address', 'company_phone', 'company_email', 'company_org_number']
 
 function fillTemplate(template: string, values: Record<string, string>): string {
   return Object.entries(values).reduce(
@@ -111,6 +113,8 @@ export function QuotePDF({
   const showLogo = template?.showLogo !== false
   const showCompanyName = template?.showCompanyName !== false
   const logoScale = (template?.logoSize || 100) / 100
+  const headerFields = layoutConfig ? getVisibleFieldsForSectionHelper(layoutConfig, 'header') : null
+  const headerFieldOrder = getOrderedFieldIds(headerFields, DEFAULT_HEADER_FIELD_ORDER)
   const styles = createStyles(primaryColor, fontFamily)
   const fontBold = getFontBold(fontFamily)
 
@@ -150,12 +154,35 @@ export function QuotePDF({
   const validDate = data.validUntil ? formatDateForPdf(data.validUntil, df, tz) : null
   const shopName = workshop?.name || 'Torqvoice'
 
+  const renderCompactField = (fieldId: string) => {
+    switch (fieldId) {
+      case 'logo':
+        return showLogo && logoDataUri ? (
+          <Image key="logo" src={logoDataUri} style={{ maxWidth: 40 * logoScale, maxHeight: 40 * logoScale, borderRadius: 4, objectFit: 'contain', marginBottom: 4 }} />
+        ) : null
+      case 'company_name':
+        return showCompanyName ? (
+          <Text key="company_name" style={{ fontSize: 16, fontFamily: fontBold, color: primaryColor }}>{shopName}</Text>
+        ) : null
+      case 'company_address':
+        return workshop?.address ? <Text key="company_address" style={{ fontSize: 8, color: gray }}>{workshop.address}</Text> : null
+      case 'company_phone':
+        return workshop?.phone ? <Text key="company_phone" style={{ fontSize: 8, color: gray }}>{labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}</Text> : null
+      case 'company_email':
+        return workshop?.email ? <Text key="company_email" style={{ fontSize: 8, color: gray }}>{workshop.email}</Text> : null
+      case 'company_org_number':
+        return null // org number not shown in quote header
+      default:
+        return null
+    }
+  }
+
   const renderCompactHeader = () => (
     <View style={{ marginBottom: 20 }}>
       <View
         style={{
           flexDirection: 'row',
-          alignItems: 'center',
+          alignItems: 'flex-start',
           justifyContent: 'space-between',
           paddingBottom: 10,
           borderBottomWidth: 1,
@@ -163,20 +190,7 @@ export function QuotePDF({
         }}
       >
         <View>
-          {showLogo && logoDataUri && (
-            <Image
-              src={logoDataUri}
-              style={{ maxWidth: 40 * logoScale, maxHeight: 40 * logoScale, borderRadius: 4, objectFit: 'contain', marginBottom: 4 }}
-            />
-          )}
-          {showCompanyName && (
-            <Text style={{ fontSize: 16, fontFamily: fontBold, color: primaryColor }}>
-              {shopName}
-            </Text>
-          )}
-          {workshop?.address && (
-            <Text style={{ fontSize: 8, color: gray }}>{workshop.address}</Text>
-          )}
+          {headerFieldOrder.map(renderCompactField)}
         </View>
         <View style={{ alignItems: 'flex-end' }}>
           <Text style={{ fontSize: 14, fontFamily: fontBold, color: primaryColor }}>{labels.title || 'QUOTE'}</Text>
@@ -185,32 +199,44 @@ export function QuotePDF({
           {validDate && <Text style={{ fontSize: 9, color: gray }}>{labels.validUntil ? fillTemplate(labels.validUntil, { date: validDate }) : `Valid until: ${validDate}`}</Text>}
         </View>
       </View>
-      <View
-        style={{
-          flexDirection: 'row',
-          justifyContent: 'space-between',
-          marginTop: 6,
-          paddingHorizontal: 2,
-        }}
-      >
-        <View style={{ flexDirection: 'row', gap: 12 }}>
-          {workshop?.phone && (
-            <Text style={{ fontSize: 8, color: gray }}>{labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}</Text>
-          )}
-          {workshop?.email && (
-            <Text style={{ fontSize: 8, color: gray }}>{workshop.email}</Text>
-          )}
+      {torqvoiceLogoDataUri && (
+        <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', gap: 3, marginTop: 6, paddingHorizontal: 2 }}>
+          <Image src={torqvoiceLogoDataUri} style={{ width: 12, height: 12 }} />
+          <Text style={{ fontSize: 7, fontFamily: fontBold, color: gray }}>Torqvoice</Text>
         </View>
-        {torqvoiceLogoDataUri && (
-          <View style={{ flexDirection: 'row', alignItems: 'center', gap: 3 }}>
-            <Image src={torqvoiceLogoDataUri} style={{ width: 12, height: 12 }} />
-            <Text style={{ fontSize: 7, fontFamily: fontBold, color: gray }}>Torqvoice</Text>
-          </View>
-        )}
-      </View>
+      )}
     </View>
   )
 
+  const renderModernField = (fieldId: string) => {
+    switch (fieldId) {
+      case 'logo':
+        return showLogo && logoDataUri ? (
+          <Image key="logo" src={logoDataUri} style={{ maxWidth: 50 * logoScale, maxHeight: 50 * logoScale, borderRadius: 4, objectFit: 'contain', marginBottom: 6 }} />
+        ) : null
+      case 'company_name':
+        return showCompanyName ? (
+          <Text key="company_name" style={{ fontSize: 22, fontFamily: fontBold, color: 'white' }}>{shopName}</Text>
+        ) : null
+      case 'company_address':
+        return workshop?.address ? (
+          <Text key="company_address" style={{ fontSize: 9, color: 'rgba(255,255,255,0.8)', marginTop: 2 }}>{workshop.address}</Text>
+        ) : null
+      case 'company_phone':
+        return workshop?.phone ? (
+          <Text key="company_phone" style={{ fontSize: 8, color: 'rgba(255,255,255,0.7)', marginTop: 2 }}>{labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}</Text>
+        ) : null
+      case 'company_email':
+        return workshop?.email ? (
+          <Text key="company_email" style={{ fontSize: 8, color: 'rgba(255,255,255,0.7)', marginTop: 2 }}>{workshop.email}</Text>
+        ) : null
+      case 'company_org_number':
+        return null // org number not shown in quote header
+      default:
+        return null
+    }
+  }
+
   const renderModernHeader = () => (
     <View style={{ marginBottom: 24 }}>
       <View
@@ -222,34 +248,7 @@ export function QuotePDF({
         }}
       >
         <View style={{ alignItems: 'center' }}>
-          {showLogo && logoDataUri && (
-            <Image
-              src={logoDataUri}
-              style={{ maxWidth: 50 * logoScale, maxHeight: 50 * logoScale, borderRadius: 4, objectFit: 'contain', marginBottom: 6 }}
-            />
-          )}
-          {showCompanyName && (
-            <Text style={{ fontSize: 22, fontFamily: fontBold, color: 'white' }}>
-              {shopName}
-            </Text>
-          )}
-          {workshop?.address && (
-            <Text style={{ fontSize: 9, color: 'rgba(255,255,255,0.8)', marginTop: 2 }}>
-              {workshop.address}
-            </Text>
-          )}
-          <View style={{ flexDirection: 'row', gap: 12, marginTop: 4 }}>
-            {workshop?.phone && (
-              <Text style={{ fontSize: 8, color: 'rgba(255,255,255,0.7)' }}>
-                {labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}
-              </Text>
-            )}
-            {workshop?.email && (
-              <Text style={{ fontSize: 8, color: 'rgba(255,255,255,0.7)' }}>
-                {workshop.email}
-              </Text>
-            )}
-          </View>
+          {headerFieldOrder.map(renderModernField)}
         </View>
         {torqvoiceLogoDataUri && (
           <View
@@ -287,26 +286,31 @@ export function QuotePDF({
     </View>
   )
 
+  const renderStandardField = (fieldId: string) => {
+    switch (fieldId) {
+      case 'logo':
+        return showLogo && logoDataUri ? (
+          <Image key="logo" src={logoDataUri} style={{ maxWidth: 150 * logoScale, maxHeight: 60 * logoScale, marginBottom: 6, borderRadius: 4, objectFit: 'contain', objectPosition: 'left' }} />
+        ) : null
+      case 'company_name':
+        return showCompanyName ? <Text key="company_name" style={styles.brandName}>{shopName}</Text> : null
+      case 'company_address':
+        return workshop?.address ? <Text key="company_address" style={styles.brandSub}>{workshop.address}</Text> : null
+      case 'company_phone':
+        return workshop?.phone ? <Text key="company_phone" style={styles.brandContact}>{labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}</Text> : null
+      case 'company_email':
+        return workshop?.email ? <Text key="company_email" style={styles.brandContact}>{workshop.email}</Text> : null
+      case 'company_org_number':
+        return null // org number not shown in quote header
+      default:
+        return null
+    }
+  }
+
   const renderStandardHeader = () => (
     <View style={styles.header}>
       <View>
-        {showLogo && logoDataUri && (
-          <Image
-            src={logoDataUri}
-            style={{
-              maxWidth: 150 * logoScale,
-              maxHeight: 60 * logoScale,
-              marginBottom: 6,
-              borderRadius: 4,
-              objectFit: 'contain',
-              objectPosition: 'left',
-            }}
-          />
-        )}
-        {showCompanyName && <Text style={styles.brandName}>{shopName}</Text>}
-        {workshop?.address && <Text style={styles.brandSub}>{workshop.address}</Text>}
-        {workshop?.phone && <Text style={styles.brandContact}>{labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}</Text>}
-        {workshop?.email && <Text style={styles.brandContact}>{workshop.email}</Text>}
+        {headerFieldOrder.map(renderStandardField)}
       </View>
       <View>
         {torqvoiceLogoDataUri && (

+ 2 - 0
src/features/settings/Components/InvoiceLayoutEditor.tsx

@@ -318,6 +318,7 @@ function FieldsPanel({
   return (
     <div className="mt-1 space-y-1">
       <DndContext
+        id={`layout-fields-${section.id}`}
         sensors={fieldSensors}
         collisionDetection={closestCenter}
         onDragEnd={handleFieldDragEnd}
@@ -679,6 +680,7 @@ export function InvoiceLayoutEditor({
       </p>
 
       <DndContext
+        id="layout-sections"
         sensors={sensors}
         collisionDetection={closestCenter}
         onDragEnd={handleDragEnd}

+ 1 - 1
src/features/settings/Components/InvoiceLayoutPreview.tsx

@@ -41,7 +41,7 @@ const PreviewRenderer = dynamic(
   {
     ssr: false,
     loading: () => (
-      <div className="flex h-[920px] items-center justify-center rounded-lg border bg-gray-100 text-sm text-muted-foreground">
+      <div className="flex h-230 items-center justify-center rounded-lg border bg-gray-100 text-sm text-muted-foreground">
         Loading preview…
       </div>
     ),

+ 4 - 0
src/features/settings/Schema/invoiceLayoutSchema.ts

@@ -99,6 +99,10 @@ export const BUILTIN_INFO_FIELDS = [
 export const BUILTIN_HEADER_FIELDS = [
   { id: "logo", name: "Logo" },
   { id: "company_name", name: "Company Name" },
+  { id: "company_address", name: "Address" },
+  { id: "company_phone", name: "Phone" },
+  { id: "company_email", name: "Email" },
+  { id: "company_org_number", name: "Organization Number" },
 ] as const;
 
 export const BUILTIN_BANK_ACCOUNT_FIELDS = [

+ 0 - 1
src/features/settings/Schema/settingsSchema.ts

@@ -20,7 +20,6 @@ export const SETTING_KEYS = {
   INVOICE_DUE_DAYS: "invoice.dueDays",
   UNIT_SYSTEM: "workshop.unitSystem",
   DEFAULT_TECHNICIAN: "workshop.defaultTechnician",
-  WORKING_HOURS: "workshop.workingHours",
   DEFAULT_LABOR_RATE: "workshop.defaultLaborRate",
   QUOTE_PREFIX: "workshop.quotePrefix",
   QUOTE_VALID_DAYS: "workshop.quoteValidDays",

+ 149 - 96
src/features/vehicles/Components/invoice-pdf/Header.tsx

@@ -2,6 +2,7 @@ import { Text, View, Image } from '@react-pdf/renderer'
 import type { WorkshopInfo, InvoiceSettingsProps } from './types'
 import { gray, getFontBold } from './styles'
 import type { Style } from '@react-pdf/types'
+import { getOrderedFieldIds } from '@/features/settings/Schema/invoiceLayoutSchema'
 
 function fillTemplate(template: string, values: Record<string, string>): string {
   return Object.entries(values).reduce(
@@ -10,6 +11,8 @@ function fillTemplate(template: string, values: Record<string, string>): string
   )
 }
 
+const DEFAULT_HEADER_FIELD_ORDER = ['logo', 'company_name', 'company_address', 'company_phone', 'company_email', 'company_org_number']
+
 interface HeaderProps {
   headerStyle: string
   primaryColor: string
@@ -55,11 +58,53 @@ export function Header({
   // Logo size scale factor (default 100 = 1x)
   const scale = (logoSize || 100) / 100
 
-  // layoutConfig fields take priority over individual toggle props
-  const showLogo = visibleFields ? visibleFields.has('logo') : showLogoProp
-  const showCompanyName = visibleFields ? visibleFields.has('company_name') : showCompanyNameProp
+  // When visibleFields is provided (from layoutConfig), getOrderedFieldIds
+  // returns only the visible fields in the configured order.
+  // When not provided, falls back to default order with legacy prop checks.
+  const fieldOrder = getOrderedFieldIds(visibleFields, DEFAULT_HEADER_FIELD_ORDER)
+
+  // For legacy prop fallback (no layoutConfig)
+  const showLogo = visibleFields ? fieldOrder.includes('logo') : showLogoProp
+  const showCompanyName = visibleFields ? fieldOrder.includes('company_name') : showCompanyNameProp
 
   if (headerStyle === 'compact') {
+    const renderCompactField = (fieldId: string) => {
+      switch (fieldId) {
+        case 'logo':
+          return showLogo && logoDataUri ? (
+            <Image
+              key="logo"
+              src={logoDataUri}
+              style={{ maxWidth: 40 * scale, maxHeight: 40 * scale, borderRadius: 4, objectFit: 'contain', marginBottom: 4 }}
+            />
+          ) : null
+        case 'company_name':
+          return showCompanyName ? (
+            <Text key="company_name" style={{ fontSize: 16, fontFamily: fontBold, color: primaryColor }}>
+              {shopDisplayName}
+            </Text>
+          ) : null
+        case 'company_address':
+          return workshop?.address ? (
+            <Text key="company_address" style={{ fontSize: 8, color: gray }}>{workshop.address}</Text>
+          ) : null
+        case 'company_phone':
+          return workshop?.phone ? (
+            <Text key="company_phone" style={{ fontSize: 8, color: gray }}>{labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}</Text>
+          ) : null
+        case 'company_email':
+          return workshop?.email ? (
+            <Text key="company_email" style={{ fontSize: 8, color: gray }}>{workshop.email}</Text>
+          ) : null
+        case 'company_org_number':
+          return invoiceSettings?.showOrgNumber && invoiceSettings?.orgNumber ? (
+            <Text key="company_org_number" style={{ fontSize: 8, color: gray }}>{labels.org ? fillTemplate(labels.org, { org: invoiceSettings.orgNumber }) : `Org: ${invoiceSettings.orgNumber}`}</Text>
+          ) : null
+        default:
+          return null
+      }
+    }
+
     return (
       <View style={{ marginBottom: 20 }}>
         <View
@@ -73,20 +118,7 @@ export function Header({
           }}
         >
           <View>
-            {showLogo && logoDataUri && (
-              <Image
-                src={logoDataUri}
-                style={{ maxWidth: 40 * scale, maxHeight: 40 * scale, borderRadius: 4, objectFit: 'contain', marginBottom: 4 }}
-              />
-            )}
-            {showCompanyName && (
-              <Text style={{ fontSize: 16, fontFamily: fontBold, color: primaryColor }}>
-                {shopDisplayName}
-              </Text>
-            )}
-            {workshop?.address && (
-              <Text style={{ fontSize: 8, color: gray }}>{workshop.address}</Text>
-            )}
+            {fieldOrder.map(renderCompactField)}
           </View>
           <View style={{ alignItems: 'flex-end' }}>
             <Text style={{ fontSize: 14, fontFamily: fontBold }}>{labels.title || 'INVOICE'}</Text>
@@ -95,37 +127,77 @@ export function Header({
             {dueDate && <Text style={{ fontSize: 9, color: gray }}>{labels.due ? fillTemplate(labels.due, { date: dueDate }) : `Due: ${dueDate}`}</Text>}
           </View>
         </View>
-        <View
-          style={{
-            flexDirection: 'row',
-            justifyContent: 'space-between',
-            marginTop: 6,
-            paddingHorizontal: 2,
-          }}
-        >
-          <View style={{ flexDirection: 'row', gap: 12 }}>
-            {workshop?.phone && (
-              <Text style={{ fontSize: 8, color: gray }}>{labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}</Text>
-            )}
-            {workshop?.email && (
-              <Text style={{ fontSize: 8, color: gray }}>{workshop.email}</Text>
-            )}
-            {invoiceSettings?.showOrgNumber && invoiceSettings?.orgNumber && (
-              <Text style={{ fontSize: 8, color: gray }}>{labels.org ? fillTemplate(labels.org, { org: invoiceSettings.orgNumber }) : `Org: ${invoiceSettings.orgNumber}`}</Text>
-            )}
+        {torqvoiceLogoDataUri && (
+          <View
+            style={{
+              flexDirection: 'row',
+              alignItems: 'center',
+              justifyContent: 'flex-end',
+              gap: 3,
+              marginTop: 6,
+              paddingHorizontal: 2,
+            }}
+          >
+            <Image src={torqvoiceLogoDataUri} style={{ width: 12, height: 12 }} />
+            <Text style={{ fontSize: 7, fontFamily: fontBold, color: gray }}>Torqvoice</Text>
           </View>
-          {torqvoiceLogoDataUri && (
-            <View style={{ flexDirection: 'row', alignItems: 'center', gap: 3 }}>
-              <Image src={torqvoiceLogoDataUri} style={{ width: 12, height: 12 }} />
-              <Text style={{ fontSize: 7, fontFamily: fontBold, color: gray }}>Torqvoice</Text>
-            </View>
-          )}
-        </View>
+        )}
       </View>
     )
   }
 
   if (headerStyle === 'modern') {
+    const renderModernField = (fieldId: string) => {
+      switch (fieldId) {
+        case 'logo':
+          return showLogo && logoDataUri ? (
+            <Image
+              key="logo"
+              src={logoDataUri}
+              style={{
+                maxWidth: 50 * scale,
+                maxHeight: 50 * scale,
+                borderRadius: 4,
+                objectFit: 'contain',
+                marginBottom: 6,
+              }}
+            />
+          ) : null
+        case 'company_name':
+          return showCompanyName ? (
+            <Text key="company_name" style={{ fontSize: 22, fontFamily: fontBold, color: 'white' }}>
+              {shopDisplayName}
+            </Text>
+          ) : null
+        case 'company_address':
+          return workshop?.address ? (
+            <Text key="company_address" style={{ fontSize: 9, color: 'rgba(255,255,255,0.8)', marginTop: 2 }}>
+              {workshop.address}
+            </Text>
+          ) : null
+        case 'company_phone':
+          return workshop?.phone ? (
+            <Text key="company_phone" style={{ fontSize: 8, color: 'rgba(255,255,255,0.7)', marginTop: 2 }}>
+              {labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}
+            </Text>
+          ) : null
+        case 'company_email':
+          return workshop?.email ? (
+            <Text key="company_email" style={{ fontSize: 8, color: 'rgba(255,255,255,0.7)', marginTop: 2 }}>
+              {workshop.email}
+            </Text>
+          ) : null
+        case 'company_org_number':
+          return invoiceSettings?.showOrgNumber && invoiceSettings?.orgNumber ? (
+            <Text key="company_org_number" style={{ fontSize: 8, color: 'rgba(255,255,255,0.7)', marginTop: 2 }}>
+              {labels.org ? fillTemplate(labels.org, { org: invoiceSettings.orgNumber }) : `Org: ${invoiceSettings.orgNumber}`}
+            </Text>
+          ) : null
+        default:
+          return null
+      }
+    }
+
     return (
       <View style={{ marginBottom: 24 }}>
         <View
@@ -137,45 +209,7 @@ export function Header({
           }}
         >
           <View style={{ alignItems: 'center' }}>
-            {showLogo && logoDataUri && (
-              <Image
-                src={logoDataUri}
-                style={{
-                  maxWidth: 50 * scale,
-                  maxHeight: 50 * scale,
-                  borderRadius: 4,
-                  objectFit: 'contain',
-                  marginBottom: 6,
-                }}
-              />
-            )}
-            {showCompanyName && (
-              <Text style={{ fontSize: 22, fontFamily: fontBold, color: 'white' }}>
-                {shopDisplayName}
-              </Text>
-            )}
-            {workshop?.address && (
-              <Text style={{ fontSize: 9, color: 'rgba(255,255,255,0.8)', marginTop: 2 }}>
-                {workshop.address}
-              </Text>
-            )}
-            <View style={{ flexDirection: 'row', gap: 12, marginTop: 4 }}>
-              {workshop?.phone && (
-                <Text style={{ fontSize: 8, color: 'rgba(255,255,255,0.7)' }}>
-                  {labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}
-                </Text>
-              )}
-              {workshop?.email && (
-                <Text style={{ fontSize: 8, color: 'rgba(255,255,255,0.7)' }}>
-                  {workshop.email}
-                </Text>
-              )}
-              {invoiceSettings?.showOrgNumber && invoiceSettings?.orgNumber && (
-                <Text style={{ fontSize: 8, color: 'rgba(255,255,255,0.7)' }}>
-                  {labels.org ? fillTemplate(labels.org, { org: invoiceSettings.orgNumber }) : `Org: ${invoiceSettings.orgNumber}`}
-                </Text>
-              )}
-            </View>
+            {fieldOrder.map(renderModernField)}
           </View>
           {torqvoiceLogoDataUri && (
             <View
@@ -215,11 +249,12 @@ export function Header({
   }
 
   // Standard header (default)
-  return (
-    <View style={styles.header}>
-      <View>
-        {showLogo && logoDataUri && (
+  const renderStandardField = (fieldId: string) => {
+    switch (fieldId) {
+      case 'logo':
+        return showLogo && logoDataUri ? (
           <Image
+            key="logo"
             src={logoDataUri}
             style={{
               maxWidth: 150 * scale,
@@ -230,18 +265,36 @@ export function Header({
               objectPosition: 'left',
             }}
           />
-        )}
-        {showCompanyName && <Text style={styles.brandName}>{shopDisplayName}</Text>}
-        {workshop?.address ? (
-          <Text style={styles.brandSub}>{workshop.address}</Text>
+        ) : null
+      case 'company_name':
+        return showCompanyName ? <Text key="company_name" style={styles.brandName}>{shopDisplayName}</Text> : null
+      case 'company_address':
+        return workshop?.address ? (
+          <Text key="company_address" style={styles.brandSub}>{workshop.address}</Text>
         ) : (
-          <Text style={styles.brandSub}>{labels.professionalWorkshop || 'Professional Workshop Management'}</Text>
-        )}
-        {workshop?.phone && <Text style={styles.brandContact}>{labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}</Text>}
-        {workshop?.email && <Text style={styles.brandContact}>{workshop.email}</Text>}
-        {invoiceSettings?.showOrgNumber && invoiceSettings?.orgNumber && (
-          <Text style={styles.brandContact}>{labels.org ? fillTemplate(labels.org, { org: invoiceSettings.orgNumber }) : `Org: ${invoiceSettings.orgNumber}`}</Text>
-        )}
+          <Text key="company_address" style={styles.brandSub}>{labels.professionalWorkshop || 'Professional Workshop Management'}</Text>
+        )
+      case 'company_phone':
+        return workshop?.phone ? (
+          <Text key="company_phone" style={styles.brandContact}>{labels.tel ? fillTemplate(labels.tel, { phone: workshop.phone }) : `Tel: ${workshop.phone}`}</Text>
+        ) : null
+      case 'company_email':
+        return workshop?.email ? (
+          <Text key="company_email" style={styles.brandContact}>{workshop.email}</Text>
+        ) : null
+      case 'company_org_number':
+        return invoiceSettings?.showOrgNumber && invoiceSettings?.orgNumber ? (
+          <Text key="company_org_number" style={styles.brandContact}>{labels.org ? fillTemplate(labels.org, { org: invoiceSettings.orgNumber }) : `Org: ${invoiceSettings.orgNumber}`}</Text>
+        ) : null
+      default:
+        return null
+    }
+  }
+
+  return (
+    <View style={styles.header}>
+      <View>
+        {fieldOrder.map(renderStandardField)}
         {torqvoiceLogoDataUri && (
           <View
             style={{