mobile responsive

This commit is contained in:
Rajpreet0 2026-09-29 22:56:37 +02:00
parent 496ad28723
commit b6f741be8f
30 changed files with 245 additions and 159 deletions

View file

@ -50,7 +50,7 @@ function ResetPasswordContent({ client, onDone }: { client: Client; onDone: () =
<input type="hidden" name="profileId" value={client.id} />
<input type="hidden" name="mode" value={mode} />
<FieldGroup>
<div role="radiogroup" aria-label="Art des neuen Passworts" className="grid grid-cols-2 gap-2">
<div role="radiogroup" aria-label="Art des neuen Passworts" className="grid gap-2 sm:grid-cols-2">
{(["generate", "manual"] as const).map((value) => (
<label
key={value}

View file

@ -51,9 +51,9 @@ export function ClientsTable({ clients }: { clients: Client[] }) {
}, [clients, query]);
return (
<div className="flex flex-col gap-8 w-full">
<div className="flex flex-row items-center justify-between gap-2 w-full p-2">
<InputGroup className="max-w-lg rounded-md focus-visible:ring-0">
<div className="flex w-full flex-col gap-6 md:gap-8">
<div className="flex w-full flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<InputGroup className="w-full rounded-md focus-visible:ring-0 sm:max-w-lg">
<InputGroupInput
placeholder="Search..."
value={query}
@ -75,8 +75,8 @@ export function ClientsTable({ clients }: { clients: Client[] }) {
<TableHeader>
<TableRow>
<TableHead>Client</TableHead>
<TableHead>Status</TableHead>
<TableHead className="w-24" />
<TableHead className="hidden sm:table-cell">Status</TableHead>
<TableHead className="w-20 sm:w-24" />
</TableRow>
</TableHeader>
<TableBody>
@ -86,7 +86,7 @@ export function ClientsTable({ clients }: { clients: Client[] }) {
return (
<TableRow key={client.id}>
<TableCell>
<TableCell className="w-full max-w-0 sm:w-auto sm:max-w-none">
<div className="flex items-center gap-3">
<Avatar size="lg" className="shrink-0">
<AvatarFallback>
@ -99,7 +99,7 @@ export function ClientsTable({ clients }: { clients: Client[] }) {
</div>
</div>
</TableCell>
<TableCell>
<TableCell className="hidden sm:table-cell">
<Badge variant={status.variant}>{status.label}</Badge>
</TableCell>
<TableCell>

View file

@ -256,7 +256,7 @@ function ActiveToggle({ template }: { template: AdminProductTemplate }) {
export function ProductTemplatesSection({ templates }: { templates: AdminProductTemplate[] }) {
return (
<div className="flex flex-col gap-4">
<div className="flex justify-end">
<div className="flex sm:justify-end">
<TemplateDialog />
</div>
{templates.length === 0 ? (
@ -267,8 +267,8 @@ export function ProductTemplatesSection({ templates }: { templates: AdminProduct
<ul className="flex flex-col divide-y divide-border rounded-lg border border-border">
{templates.map((template) => (
<li key={template.id} className={cn("flex flex-wrap items-center gap-3 px-4 py-3", !template.isActive && "opacity-60")}>
<div className="flex min-w-0 flex-1 flex-col">
<span className="flex items-center gap-2 font-medium">
<div className="flex min-w-0 flex-1 basis-full flex-col sm:basis-auto">
<span className="flex flex-wrap items-center gap-2 font-medium break-words">
{template.name}
{!template.isActive && <Badge>Inaktiv</Badge>}
</span>
@ -278,7 +278,7 @@ export function ProductTemplatesSection({ templates }: { templates: AdminProduct
<span className="w-20 text-right text-small text-muted-foreground tabular-nums">
{template.taxRate.toLocaleString("de-DE")} % MwSt
</span>
<div className="flex items-center gap-1">
<div className="ml-auto flex items-center gap-1">
<ActiveToggle template={template} />
<TemplateDialog template={template} />
<DeleteTemplateDialog template={template} />

View file

@ -5,8 +5,8 @@ const ClientListViewPage = async () => {
const clients = await getClients()
return (
<div className="flex flex-col gap-8 w-full">
<h1 className="text-3xl font-semibold">Clients</h1>
<div className="flex w-full flex-col gap-6 md:gap-8">
<h1 className="text-2xl font-semibold md:text-3xl">Clients</h1>
<ClientsTable clients={clients} />
</div>
)

View file

@ -46,7 +46,7 @@ function StatCard({
<span className="text-small text-muted-foreground">{label}</span>
<span
className={cn(
"text-2xl font-semibold tabular-nums",
"text-xl font-semibold tabular-nums break-words sm:text-2xl",
tone === "danger" && "text-signal-error",
tone === "success" && "text-signal-success"
)}
@ -57,7 +57,7 @@ function StatCard({
</>
)
const className = "flex flex-col gap-1 rounded-lg border border-border p-4"
const className = "flex min-w-0 flex-col gap-1 rounded-lg border border-border p-4"
return href ? (
<Link href={href} className={cn(className, "transition-colors hover:bg-surface")}>{content}</Link>
) : (
@ -68,7 +68,7 @@ function StatCard({
function Section({ title, count, children }: { title: string; count?: number; children: React.ReactNode }) {
return (
<section className="flex flex-col gap-3">
<h2 className="flex items-center gap-2 text-xl font-semibold">
<h2 className="flex items-center gap-2 text-lg font-semibold md:text-xl">
{title}
{count !== undefined && count > 0 && <Badge>{count}</Badge>}
</h2>
@ -101,13 +101,13 @@ const DashboardView = async ({ name }: { name: string }) => {
const today = berlinToday()
return (
<div className="flex flex-col gap-10 w-full">
<div className="flex w-full flex-col gap-8 md:gap-10">
<div className="flex flex-col gap-2">
<h1 className="text-3xl font-semibold">Hallo {name.split(" ")[0]}</h1>
<h1 className="text-2xl font-semibold md:text-3xl">Hallo {name.split(" ")[0]}</h1>
<p className="text-accent-400">Alles Wichtige über alle Clients auf einen Blick.</p>
</div>
<div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
<div className="grid gap-3 min-[480px]:grid-cols-2 xl:grid-cols-4">
<StatCard label="Offene Rechnungen" value={formatCurrency(data.openTotal)} detail={`${data.openCount} ${data.openCount === 1 ? "Rechnung" : "Rechnungen"}`} />
<StatCard
label="Davon überfällig"
@ -148,7 +148,7 @@ const DashboardView = async ({ name }: { name: string }) => {
)}
</Section>
<div className="grid gap-10 lg:grid-cols-2">
<div className="grid gap-8 md:gap-10 xl:grid-cols-2">
<Section title="Neueste Uploads von Clients">
{data.uploads.length === 0 ? (
<EmptyHint icon={UploadIcon}>Noch keine Uploads von Clients.</EmptyHint>
@ -179,15 +179,17 @@ const DashboardView = async ({ name }: { name: string }) => {
<ul className="flex flex-col divide-y divide-border overflow-hidden rounded-lg border border-border">
{data.tasks.map((task) => (
<LinkRow key={task.id} href={task.href}>
<Badge variant={task.status === "in_progress" ? "warning" : "neutral"} className="w-20">
<Badge variant={task.status === "in_progress" ? "warning" : "neutral"} className="w-20 shrink-0">
{TASK_STATUS_LABELS[task.status]}
</Badge>
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate font-medium">{task.name}</span>
<span className="truncate text-small text-muted-foreground">{task.clientName}</span>
</span>
<span className="shrink-0 text-small text-muted-foreground">
{formatTaskTiming(task.status, task.since, today)}
<span className="flex min-w-0 flex-1 flex-col sm:flex-row sm:items-center sm:gap-3">
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate font-medium">{task.name}</span>
<span className="truncate text-small text-muted-foreground">{task.clientName}</span>
</span>
<span className="text-small text-muted-foreground sm:shrink-0">
{formatTaskTiming(task.status, task.since, today)}
</span>
</span>
</LinkRow>
))}

View file

@ -17,8 +17,8 @@ const SettingsView = async ({ name, email }: { name: string; email: string }) =>
const templates = await getAllProductTemplates()
return (
<div className="flex flex-col gap-8 w-full max-w-4xl">
<h1 className="text-3xl font-semibold">Einstellungen</h1>
<div className="flex w-full max-w-4xl flex-col gap-6 md:gap-8">
<h1 className="text-2xl font-semibold md:text-3xl">Einstellungen</h1>
<Section title="Profil" description="So wirst du im Portal angezeigt, z. B. bei „zuletzt bearbeitet von“.">
<ProfileForm name={name} email={email} />