mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 21:41:16 +00:00
mobile responsive
This commit is contained in:
parent
496ad28723
commit
b6f741be8f
30 changed files with 245 additions and 159 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue