WFDASH-WEBAPP/src/app/layout.tsx
SJCoetz cdaa2e2b25 fix, feat, style: standardize quote numbers, polish UI
- Standardize quote number format from Q-YYYY-MM-NNN to QYYYYMMNNN across all client, server, test and API code
- Add atomic backfill logic for quote counters to avoid sequence collisions with existing records
- Rearrange Delivery Notes and Repairs to the Operations sidebar section
- Add supplier count badge to pricelist catalogue and improve filter tab overflow styling
- Optimize font loading in root layout to skip preloading unused font weights
- Improve table styling for customer and company pages with better text wrapping and empty states
- Fix rep display logic in quotes page to show "Unassigned" when no rep is assigned
- Add supporting documentation, PocketBase counter schemas and setup migration scripts
- Remove embedded SupplierMap from suppliers page for cleaner layout
- Improve invoice card layout for company and customer info spacing
- Improve filter label styling and accessibility in collection map page
2026-07-10 22:21:50 +02:00

179 lines
4.8 KiB
TypeScript

import type { Metadata, Viewport } from "next";
import {
Geist,
Geist_Mono,
Inter,
Space_Grotesk,
DM_Sans,
Playfair_Display,
Lato,
Nunito,
Outfit,
JetBrains_Mono,
} from "next/font/google";
import "leaflet/dist/leaflet.css";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
import { AuthProvider } from "@/components/auth-provider";
import { Toaster } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { ServiceWorkerRegistration } from "@/components/service-worker-registration";
import { ThemeBootstrapScript } from "@/components/theme-bootstrap";
// Geist (default app fonts)
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
// Theme-bound fonts (registered as CSS vars for the theme picker to swap).
// Each font is restricted to the two weights actually used in the app
// (400 regular + 700 bold). Without these, next/font/google preloads
// every available weight and the browser logs warnings about unused
// preload tags (and the page wastes bandwidth fetching woff2 files
// that will never render).
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
weight: ["400", "700"],
display: "swap",
});
const spaceGrotesk = Space_Grotesk({
variable: "--font-space-grotesk",
subsets: ["latin"],
weight: ["400", "700"],
display: "swap",
});
const dmSans = DM_Sans({
variable: "--font-dm-sans",
subsets: ["latin"],
weight: ["400", "700"],
display: "swap",
});
const playfair = Playfair_Display({
variable: "--font-playfair",
subsets: ["latin"],
weight: ["400", "700"],
display: "swap",
});
const lato = Lato({
variable: "--font-lato",
subsets: ["latin"],
weight: ["400", "700"],
display: "swap",
});
const nunito = Nunito({
variable: "--font-nunito",
subsets: ["latin"],
weight: ["400", "700"],
display: "swap",
});
const outfit = Outfit({
variable: "--font-outfit",
subsets: ["latin"],
weight: ["400", "700"],
display: "swap",
});
const jetbrainsMono = JetBrains_Mono({
variable: "--font-jetbrains-mono",
subsets: ["latin"],
weight: ["400", "700"],
display: "swap",
});
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
viewportFit: "cover",
themeColor: "#4C96D7",
};
export const metadata: Metadata = {
title: "WFDASH - Business CRM",
description: "Business CRM for managing customers, companies, suppliers, quotes, and orders",
manifest: "/manifest.json",
applicationName: "WFDASH",
authors: [{ name: "WFDASH" }],
keywords: ["CRM", "Business", "Quotes", "Orders", "Customers", "Suppliers"],
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: "WFDASH",
},
icons: {
icon: [
{ url: "/favicon.ico", sizes: "any" },
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
{ url: "/favicon-96x96.png", sizes: "96x96", type: "image/png" },
{ url: "/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
{ url: "/android-chrome-512x512.png", sizes: "512x512", type: "image/png" },
],
apple: "/apple-touch-icon.png",
},
openGraph: {
type: "website",
title: "WFDASH - Business CRM",
description: "Business CRM for managing customers, companies, suppliers, quotes, and orders",
siteName: "WFDASH",
images: [
{
url: "/og-image.png",
width: 1200,
height: 630,
alt: "WFDASH - Business CRM",
},
],
},
twitter: {
card: "summary_large_image",
title: "WFDASH - Business CRM",
description: "Business CRM for managing customers, companies, suppliers, quotes, and orders",
images: ["/twitter-image.png"],
},
};
// All theme-bound CSS vars must be present on <html> so themes can swap them
const FONT_VARS = [
geistSans.variable,
geistMono.variable,
inter.variable,
spaceGrotesk.variable,
dmSans.variable,
playfair.variable,
lato.variable,
nunito.variable,
outfit.variable,
jetbrainsMono.variable,
].join(" ");
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html
lang="en"
className={`${FONT_VARS} h-full antialiased`}
data-theme="crisp"
suppressHydrationWarning
>
<head>
{/* Apply persisted theme before hydration to prevent flash of wrong theme */}
<ThemeBootstrapScript />
</head>
<body className="min-h-full flex flex-col">
<ServiceWorkerRegistration />
<ThemeProvider>
<TooltipProvider>
<AuthProvider>
{children}
</AuthProvider>
</TooltipProvider>
<Toaster richColors />
</ThemeProvider>
</body>
</html>
);
}