소스 검색

Fix PDF preview font rendering (#76)

* fix preview bug

* add search to dropdown
Bernt Christian Egeland 6 달 전
부모
커밋
07fda5512d

+ 4 - 0
messages/de/settings.json

@@ -556,6 +556,8 @@
     "title": "Wahrung & Steuer",
     "description": "Standardwahrung und Steuersatz fur Rechnungen und Servicedaten.",
     "currencyLabel": "Wahrung",
+    "searchCurrency": "Währung suchen...",
+    "noCurrencyFound": "Keine Währung gefunden.",
     "previewLabel": "Vorschau: {value}",
     "enableTax": "Steuer aktivieren",
     "enableTaxHint": "Steuerberechnung auf Rechnungen und Servicedaten anzeigen",
@@ -711,6 +713,8 @@
     "time24h": "24-Stunden (14:30)",
     "timezone": "Zeitzone",
     "timezoneAuto": "Automatisch erkennen (Browser)",
+    "searchTimezone": "Zeitzone suchen...",
+    "noTimezoneFound": "Keine Zeitzone gefunden.",
     "timezoneHint": "Bei automatischer Erkennung sieht jeder Benutzer Daten in der Zeitzone seines Browsers.",
     "previewLabel": "Vorschau",
     "dateLabel": "Datum",

+ 4 - 0
messages/en/settings.json

@@ -556,6 +556,8 @@
     "title": "Currency & Tax",
     "description": "Default currency and tax rate used across invoices and service records.",
     "currencyLabel": "Currency",
+    "searchCurrency": "Search currency...",
+    "noCurrencyFound": "No currency found.",
     "previewLabel": "Preview: {value}",
     "enableTax": "Enable Tax",
     "enableTaxHint": "Show tax calculation on invoices and service records",
@@ -711,6 +713,8 @@
     "time24h": "24-hour (14:30)",
     "timezone": "Timezone",
     "timezoneAuto": "Auto-detect (browser)",
+    "searchTimezone": "Search timezone...",
+    "noTimezoneFound": "No timezone found.",
     "timezoneHint": "When set to auto-detect, each user sees dates in their browser's timezone.",
     "previewLabel": "Preview",
     "dateLabel": "Date",

+ 4 - 0
messages/es/settings.json

@@ -556,6 +556,8 @@
     "title": "Moneda e impuestos",
     "description": "Moneda predeterminada y tasa de impuestos para facturas y registros de servicio.",
     "currencyLabel": "Moneda",
+    "searchCurrency": "Buscar moneda...",
+    "noCurrencyFound": "No se encontró moneda.",
     "previewLabel": "Vista previa: {value}",
     "enableTax": "Habilitar impuestos",
     "enableTaxHint": "Mostrar calculo de impuestos en facturas y registros de servicio",
@@ -711,6 +713,8 @@
     "time24h": "24 horas (14:30)",
     "timezone": "Zona horaria",
     "timezoneAuto": "Detectar automaticamente (navegador)",
+    "searchTimezone": "Buscar zona horaria...",
+    "noTimezoneFound": "No se encontró zona horaria.",
     "timezoneHint": "Con deteccion automatica, cada usuario ve las fechas en la zona horaria de su navegador.",
     "previewLabel": "Vista previa",
     "dateLabel": "Fecha",

+ 4 - 0
messages/fr/settings.json

@@ -556,6 +556,8 @@
     "title": "Devise et taxes",
     "description": "Devise par defaut et taux d'imposition pour les factures et les dossiers de service.",
     "currencyLabel": "Devise",
+    "searchCurrency": "Rechercher une devise...",
+    "noCurrencyFound": "Aucune devise trouvée.",
     "previewLabel": "Apercu : {value}",
     "enableTax": "Activer les taxes",
     "enableTaxHint": "Afficher le calcul des taxes sur les factures et les dossiers de service",
@@ -711,6 +713,8 @@
     "time24h": "24 heures (14:30)",
     "timezone": "Fuseau horaire",
     "timezoneAuto": "Detection automatique (navigateur)",
+    "searchTimezone": "Rechercher un fuseau horaire...",
+    "noTimezoneFound": "Aucun fuseau horaire trouvé.",
     "timezoneHint": "En mode auto-detection, chaque utilisateur voit les dates dans le fuseau horaire de son navigateur.",
     "previewLabel": "Apercu",
     "dateLabel": "Date",

+ 4 - 0
messages/it/settings.json

@@ -556,6 +556,8 @@
     "title": "Valuta e imposte",
     "description": "Valuta e aliquota fiscale predefinite utilizzate in fatture e registri di servizio.",
     "currencyLabel": "Valuta",
+    "searchCurrency": "Cerca valuta...",
+    "noCurrencyFound": "Nessuna valuta trovata.",
     "previewLabel": "Anteprima: {value}",
     "enableTax": "Abilita imposte",
     "enableTaxHint": "Mostra calcolo imposte su fatture e registri di servizio",
@@ -711,6 +713,8 @@
     "time24h": "24 ore (14:30)",
     "timezone": "Fuso orario",
     "timezoneAuto": "Rilevamento automatico (browser)",
+    "searchTimezone": "Cerca fuso orario...",
+    "noTimezoneFound": "Nessun fuso orario trovato.",
     "timezoneHint": "Con il rilevamento automatico, ogni utente vede le date nel fuso orario del proprio browser.",
     "previewLabel": "Anteprima",
     "dateLabel": "Data",

+ 4 - 0
messages/nb/settings.json

@@ -556,6 +556,8 @@
     "title": "Valuta og avgift",
     "description": "Standardvaluta og avgiftssats for fakturaer og servicedata.",
     "currencyLabel": "Valuta",
+    "searchCurrency": "Søk etter valuta...",
+    "noCurrencyFound": "Ingen valuta funnet.",
     "previewLabel": "Forhandsvisning: {value}",
     "enableTax": "Aktiver avgift",
     "enableTaxHint": "Vis avgiftsberegning pa fakturaer og servicedata",
@@ -711,6 +713,8 @@
     "time24h": "24-timers (14:30)",
     "timezone": "Tidssone",
     "timezoneAuto": "Automatisk gjenkjenning (nettleser)",
+    "searchTimezone": "Søk etter tidssone...",
+    "noTimezoneFound": "Ingen tidssone funnet.",
     "timezoneHint": "Med automatisk gjenkjenning ser hver bruker datoer i nettleserens tidssone.",
     "previewLabel": "Forhandsvisning",
     "dateLabel": "Dato",

+ 4 - 0
messages/nl/settings.json

@@ -556,6 +556,8 @@
     "title": "Valuta en belasting",
     "description": "Standaardvaluta en belastingtarief voor facturen en servicerecords.",
     "currencyLabel": "Valuta",
+    "searchCurrency": "Valuta zoeken...",
+    "noCurrencyFound": "Geen valuta gevonden.",
     "previewLabel": "Voorbeeld: {value}",
     "enableTax": "Belasting inschakelen",
     "enableTaxHint": "Toon belastingberekening op facturen en servicerecords",
@@ -711,6 +713,8 @@
     "time24h": "24-uurs (14:30)",
     "timezone": "Tijdzone",
     "timezoneAuto": "Automatisch detecteren (browser)",
+    "searchTimezone": "Tijdzone zoeken...",
+    "noTimezoneFound": "Geen tijdzone gevonden.",
     "timezoneHint": "Bij automatische detectie ziet elke gebruiker datums in de tijdzone van de browser.",
     "previewLabel": "Voorbeeld",
     "dateLabel": "Datum",

+ 4 - 0
messages/pl/settings.json

@@ -556,6 +556,8 @@
     "title": "Waluta i podatki",
     "description": "Domyslna waluta i stawka podatkowa dla faktur i rekordow serwisowych.",
     "currencyLabel": "Waluta",
+    "searchCurrency": "Szukaj waluty...",
+    "noCurrencyFound": "Nie znaleziono waluty.",
     "previewLabel": "Podglad: {value}",
     "enableTax": "Wlacz podatek",
     "enableTaxHint": "Pokaz obliczanie podatku na fakturach i rekordach serwisowych",
@@ -711,6 +713,8 @@
     "time24h": "24-godzinny (14:30)",
     "timezone": "Strefa czasowa",
     "timezoneAuto": "Automatyczne wykrywanie (przegladarka)",
+    "searchTimezone": "Szukaj strefy czasowej...",
+    "noTimezoneFound": "Nie znaleziono strefy czasowej.",
     "timezoneHint": "Przy automatycznym wykrywaniu kazdy uzytkownik widzi daty w strefie czasowej przegladarki.",
     "previewLabel": "Podglad",
     "dateLabel": "Data",

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

@@ -556,6 +556,8 @@
     "title": "Moeda e impostos",
     "description": "Moeda padrao e taxa de imposto para faturas e registros de servico.",
     "currencyLabel": "Moeda",
+    "searchCurrency": "Pesquisar moeda...",
+    "noCurrencyFound": "Nenhuma moeda encontrada.",
     "previewLabel": "Visualizacao: {value}",
     "enableTax": "Ativar impostos",
     "enableTaxHint": "Mostrar calculo de impostos em faturas e registros de servico",
@@ -711,6 +713,8 @@
     "time24h": "24 horas (14:30)",
     "timezone": "Fuso horario",
     "timezoneAuto": "Detectar automaticamente (navegador)",
+    "searchTimezone": "Pesquisar fuso horário...",
+    "noTimezoneFound": "Nenhum fuso horário encontrado.",
     "timezoneHint": "Com deteccao automatica, cada usuario ve as datas no fuso horario do navegador.",
     "previewLabel": "Visualizacao",
     "dateLabel": "Data",

+ 4 - 0
messages/ru/settings.json

@@ -556,6 +556,8 @@
     "title": "Валюта и налоги",
     "description": "Валюта и налоговая ставка по умолчанию, используемые в счетах и записях обслуживания.",
     "currencyLabel": "Валюта",
+    "searchCurrency": "Поиск валюты...",
+    "noCurrencyFound": "Валюта не найдена.",
     "previewLabel": "Предпросмотр: {value}",
     "enableTax": "Включить налог",
     "enableTaxHint": "Показывать расчёт налога на счетах и записях обслуживания",
@@ -711,6 +713,8 @@
     "time24h": "24-часовой (14:30)",
     "timezone": "Часовой пояс",
     "timezoneAuto": "Автоопределение (браузер)",
+    "searchTimezone": "Поиск часового пояса...",
+    "noTimezoneFound": "Часовой пояс не найден.",
     "timezoneHint": "При автоопределении каждый пользователь видит даты в часовом поясе своего браузера.",
     "previewLabel": "Предпросмотр",
     "dateLabel": "Дата",

+ 4 - 0
messages/tr/settings.json

@@ -556,6 +556,8 @@
     "title": "Para Birimi & Vergi",
     "description": "Faturalar ve servis kayıtlarında kullanılan varsayılan para birimi ve vergi oranı.",
     "currencyLabel": "Para Birimi",
+    "searchCurrency": "Para birimi ara...",
+    "noCurrencyFound": "Para birimi bulunamadı.",
     "previewLabel": "Önizleme: {value}",
     "enableTax": "Vergiyi Etkinleştir",
     "enableTaxHint": "Faturalar ve servis kayıtlarında vergi hesaplamasını göster",
@@ -711,6 +713,8 @@
     "time24h": "24 saat (14:30)",
     "timezone": "Saat Dilimi",
     "timezoneAuto": "Otomatik algıla (tarayıcı)",
+    "searchTimezone": "Saat dilimi ara...",
+    "noTimezoneFound": "Saat dilimi bulunamadı.",
     "timezoneHint": "Otomatik algılama ayarlandığında, her kullanıcı tarihleri tarayıcısının saat diliminde görür.",
     "previewLabel": "Önizleme",
     "dateLabel": "Tarih",

BIN
public/fonts/Roboto-Bold.ttf


BIN
public/fonts/Roboto-Regular.ttf


+ 69 - 26
src/app/(authenticated)/settings/localization/localization-settings.tsx

@@ -19,7 +19,10 @@ import {
   SelectValue,
 } from "@/components/ui/select";
 import { Switch } from "@/components/ui/switch";
-import { Calendar, Clock, Coins, Globe, Loader2, Moon, Palette, Save, Sun } from "lucide-react";
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
+import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/components/ui/command";
+import { Calendar, Check, ChevronDown, Clock, Coins, Globe, Loader2, Moon, Palette, Save, Sun } from "lucide-react";
+import { cn } from "@/lib/utils";
 import { formatCurrency, formatDate, formatDateTime, DEFAULT_DATE_FORMAT } from "@/lib/format";
 import { useTheme } from "@/components/theme-provider";
 import { setLocale } from "@/i18n/actions";
@@ -127,6 +130,8 @@ export function LocalizationSettings({ settings }: { settings: Record<string, st
   const { theme, setTheme } = useTheme();
   const currentLocale = useLocale();
   const [saving, setSaving] = useState(false);
+  const [currencyOpen, setCurrencyOpen] = useState(false);
+  const [timezoneOpen, setTimezoneOpen] = useState(false);
 
   // Currency & Tax
   const [currencyCode, setCurrencyCode] = useState(settings[SETTING_KEYS.CURRENCY_CODE] || "USD");
@@ -206,18 +211,34 @@ export function LocalizationSettings({ settings }: { settings: Record<string, st
             <div className="space-y-6">
               <div className="space-y-2">
                 <Label>{t("currency.currencyLabel")}</Label>
-                <Select value={currencyCode} onValueChange={setCurrencyCode}>
-                  <SelectTrigger className="w-64">
-                    <SelectValue />
-                  </SelectTrigger>
-                  <SelectContent>
-                    {CURRENCIES.map((c) => (
-                      <SelectItem key={c.code} value={c.code}>
-                        {c.code} &mdash; {t("currency.currencies." + c.code)}
-                      </SelectItem>
-                    ))}
-                  </SelectContent>
-                </Select>
+                <Popover open={currencyOpen} onOpenChange={setCurrencyOpen}>
+                  <PopoverTrigger asChild>
+                    <Button variant="outline" role="combobox" aria-expanded={currencyOpen} className="w-64 justify-between font-normal">
+                      {currencyCode} &mdash; {t("currency.currencies." + currencyCode)}
+                      <ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+                    </Button>
+                  </PopoverTrigger>
+                  <PopoverContent className="w-64 p-0" align="start">
+                    <Command>
+                      <CommandInput placeholder={t("currency.searchCurrency")} />
+                      <CommandList>
+                        <CommandEmpty>{t("currency.noCurrencyFound")}</CommandEmpty>
+                        <CommandGroup>
+                          {CURRENCIES.map((c) => (
+                            <CommandItem
+                              key={c.code}
+                              value={`${c.code} ${t("currency.currencies." + c.code)}`}
+                              onSelect={() => { setCurrencyCode(c.code); setCurrencyOpen(false); }}
+                            >
+                              <Check className={cn("mr-2 h-4 w-4", currencyCode === c.code ? "opacity-100" : "opacity-0")} />
+                              {c.code} &mdash; {t("currency.currencies." + c.code)}
+                            </CommandItem>
+                          ))}
+                        </CommandGroup>
+                      </CommandList>
+                    </Command>
+                  </PopoverContent>
+                </Popover>
                 <p className="text-xs text-muted-foreground">
                   {t("currency.previewLabel", { value: formatCurrency(1234.56, currencyCode) })}
                 </p>
@@ -302,19 +323,41 @@ export function LocalizationSettings({ settings }: { settings: Record<string, st
 
               <div className="space-y-2">
                 <Label>{t("appearance.timezone")}</Label>
-                <Select value={timezone || "__auto__"} onValueChange={(v) => setTimezone(v === "__auto__" ? "" : v)}>
-                  <SelectTrigger className="w-72">
-                    <SelectValue />
-                  </SelectTrigger>
-                  <SelectContent>
-                    <SelectItem value="__auto__">{t("appearance.timezoneAuto")}</SelectItem>
-                    {TIMEZONE_OPTIONS.map((tz) => (
-                      <SelectItem key={tz} value={tz}>
-                        {tz.replace(/_/g, " ")}
-                      </SelectItem>
-                    ))}
-                  </SelectContent>
-                </Select>
+                <Popover open={timezoneOpen} onOpenChange={setTimezoneOpen}>
+                  <PopoverTrigger asChild>
+                    <Button variant="outline" role="combobox" aria-expanded={timezoneOpen} className="w-72 justify-between font-normal">
+                      {timezone ? timezone.replace(/_/g, " ") : t("appearance.timezoneAuto")}
+                      <ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+                    </Button>
+                  </PopoverTrigger>
+                  <PopoverContent className="w-72 p-0" align="start">
+                    <Command>
+                      <CommandInput placeholder={t("appearance.searchTimezone")} />
+                      <CommandList>
+                        <CommandEmpty>{t("appearance.noTimezoneFound")}</CommandEmpty>
+                        <CommandGroup>
+                          <CommandItem
+                            value={t("appearance.timezoneAuto")}
+                            onSelect={() => { setTimezone(""); setTimezoneOpen(false); }}
+                          >
+                            <Check className={cn("mr-2 h-4 w-4", !timezone ? "opacity-100" : "opacity-0")} />
+                            {t("appearance.timezoneAuto")}
+                          </CommandItem>
+                          {TIMEZONE_OPTIONS.map((tz) => (
+                            <CommandItem
+                              key={tz}
+                              value={tz.replace(/_/g, " ")}
+                              onSelect={() => { setTimezone(tz); setTimezoneOpen(false); }}
+                            >
+                              <Check className={cn("mr-2 h-4 w-4", timezone === tz ? "opacity-100" : "opacity-0")} />
+                              {tz.replace(/_/g, " ")}
+                            </CommandItem>
+                          ))}
+                        </CommandGroup>
+                      </CommandList>
+                    </Command>
+                  </PopoverContent>
+                </Popover>
                 <p className="text-xs text-muted-foreground">
                   {t("appearance.timezoneHint")}
                 </p>

+ 1 - 0
src/features/settings/Components/InvoiceLayoutPreviewRenderer.tsx

@@ -3,6 +3,7 @@
 import { useMemo } from "react";
 import { useMessages } from "next-intl";
 import { PDFViewer } from "@react-pdf/renderer";
+import "@/features/vehicles/Components/invoice-pdf/fonts.client";
 import { InvoicePDF } from "@/features/vehicles/Components/InvoicePDF";
 import { QuotePDF } from "@/features/quotes/Components/QuotePDF";
 import type { InvoiceLayoutPreviewProps } from "./InvoiceLayoutPreview";

+ 16 - 0
src/features/vehicles/Components/invoice-pdf/fonts.client.ts

@@ -0,0 +1,16 @@
+import { Font } from '@react-pdf/renderer'
+
+Font.register({
+  family: 'Roboto',
+  fonts: [
+    { src: '/fonts/Roboto-Regular.ttf', fontWeight: 400 },
+    { src: '/fonts/Roboto-Bold.ttf', fontWeight: 700 },
+  ],
+})
+
+Font.register({
+  family: 'Roboto-Bold',
+  src: '/fonts/Roboto-Bold.ttf',
+})
+
+Font.registerHyphenationCallback((word) => [word])