Visual redesign: indigo accent palette, lucide icons per section, lighter/softer cards and dark mode
Deploy App / deploy (push) Successful in 2m59s
Deploy App / deploy (push) Successful in 2m59s
This commit is contained in:
+30
-23
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { LogOut, PiggyBank, Plus, Settings, X } from "lucide-react";
|
||||
import { PhaseCard } from "@/components/PhaseCard";
|
||||
import { TransitionPanel } from "@/components/TransitionPanel";
|
||||
import { Dashboard } from "@/components/Dashboard";
|
||||
@@ -91,15 +92,17 @@ export function AppShell({ initialHousehold }: { initialHousehold: HouseholdInpu
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-5xl flex-1 flex-col gap-6 px-4 py-8">
|
||||
<header className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold text-zinc-900 dark:text-zinc-50">
|
||||
<h1 className="flex items-center gap-2 text-xl font-semibold text-zinc-900 dark:text-zinc-50">
|
||||
<PiggyBank className="h-6 w-6 text-indigo-600 dark:text-indigo-400" />
|
||||
Financial Planning Tool
|
||||
</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowSettings((v) => !v)}
|
||||
className="rounded-md border border-zinc-300 px-3 py-1.5 text-xs font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:text-zinc-200 dark:hover:bg-zinc-800"
|
||||
className="flex items-center gap-1.5 rounded-lg border border-zinc-300 bg-white px-3 py-1.5 text-xs font-medium text-zinc-700 shadow-sm hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-200 dark:hover:bg-zinc-700"
|
||||
>
|
||||
<Settings className="h-3.5 w-3.5" />
|
||||
Grundprofil
|
||||
</button>
|
||||
<button
|
||||
@@ -108,8 +111,9 @@ export function AppShell({ initialHousehold }: { initialHousehold: HouseholdInpu
|
||||
await api.post("/api/auth/logout");
|
||||
window.location.href = "/login";
|
||||
}}
|
||||
className="rounded-md border border-zinc-300 px-3 py-1.5 text-xs font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:text-zinc-200 dark:hover:bg-zinc-800"
|
||||
className="flex items-center gap-1.5 rounded-lg border border-zinc-300 bg-white px-3 py-1.5 text-xs font-medium text-zinc-700 shadow-sm hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-200 dark:hover:bg-zinc-700"
|
||||
>
|
||||
<LogOut className="h-3.5 w-3.5" />
|
||||
Abmelden
|
||||
</button>
|
||||
</div>
|
||||
@@ -124,15 +128,15 @@ export function AppShell({ initialHousehold }: { initialHousehold: HouseholdInpu
|
||||
)}
|
||||
|
||||
{/* Tab-Leiste */}
|
||||
<div className="flex flex-wrap items-center gap-2 border-b border-zinc-200 pb-2 dark:border-zinc-700">
|
||||
<div className="flex flex-wrap items-center gap-2 border-b border-zinc-200 pb-2 dark:border-zinc-800">
|
||||
{plans.map((p) => (
|
||||
<div key={p.id} className="flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedPlanId(p.id)}
|
||||
className={`rounded-t-md px-3 py-1.5 text-sm font-medium ${
|
||||
className={`rounded-t-lg px-3 py-1.5 text-sm font-medium transition-colors ${
|
||||
selectedPlanId === p.id
|
||||
? "bg-zinc-900 text-white dark:bg-zinc-100 dark:text-zinc-900"
|
||||
? "bg-indigo-600 text-white shadow-sm dark:bg-indigo-500"
|
||||
: "text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
}`}
|
||||
>
|
||||
@@ -142,10 +146,10 @@ export function AppShell({ initialHousehold }: { initialHousehold: HouseholdInpu
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDeletePlan(p.id)}
|
||||
className="ml-1 text-xs text-zinc-400 hover:text-red-600"
|
||||
className="ml-1 text-zinc-400 hover:text-red-600"
|
||||
aria-label="Plan loeschen"
|
||||
>
|
||||
✕
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -154,9 +158,10 @@ export function AppShell({ initialHousehold }: { initialHousehold: HouseholdInpu
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowNewPlan((v) => !v)}
|
||||
className="rounded-md px-2 py-1.5 text-sm text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-800"
|
||||
className="flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm text-indigo-600 hover:bg-indigo-50 dark:text-indigo-400 dark:hover:bg-indigo-500/10"
|
||||
>
|
||||
+ Plan
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
Plan
|
||||
</button>
|
||||
{showNewPlan && (
|
||||
<NewPlanPopover
|
||||
@@ -174,9 +179,10 @@ export function AppShell({ initialHousehold }: { initialHousehold: HouseholdInpu
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowScenario((v) => !v)}
|
||||
className="rounded-md px-2 py-1.5 text-sm text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-800"
|
||||
className="flex items-center gap-1 rounded-lg px-2 py-1.5 text-sm text-indigo-600 hover:bg-indigo-50 dark:text-indigo-400 dark:hover:bg-indigo-500/10"
|
||||
>
|
||||
+ Szenario
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
Szenario
|
||||
</button>
|
||||
{showScenario && (
|
||||
<ScenarioPopover
|
||||
@@ -235,9 +241,10 @@ export function AppShell({ initialHousehold }: { initialHousehold: HouseholdInpu
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddPhase}
|
||||
className="self-start rounded-md border border-zinc-300 px-3 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:text-zinc-200 dark:hover:bg-zinc-800"
|
||||
className="flex items-center gap-1.5 self-start rounded-lg border border-dashed border-indigo-300 bg-indigo-50/50 px-3 py-2 text-sm font-medium text-indigo-700 hover:bg-indigo-50 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300 dark:hover:bg-indigo-500/20"
|
||||
>
|
||||
+ Phase hinzufuegen
|
||||
<Plus className="h-4 w-4" />
|
||||
Phase hinzufuegen
|
||||
</button>
|
||||
|
||||
{detail.plan.phases.length > 0 && (
|
||||
@@ -252,9 +259,9 @@ export function AppShell({ initialHousehold }: { initialHousehold: HouseholdInpu
|
||||
function NewPlanPopover({ onCreate, onClose }: { onCreate: (name: string) => void; onClose: () => void }) {
|
||||
const [name, setName] = useState("Basisplan");
|
||||
return (
|
||||
<div className="absolute left-0 top-10 z-10 w-64 rounded-md border border-zinc-200 bg-white p-3 shadow-lg dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="absolute left-0 top-10 z-10 w-64 rounded-lg border border-zinc-200 bg-white p-3 shadow-lg dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<input
|
||||
className="mb-2 w-full rounded-md border border-zinc-300 px-2 py-1 text-sm dark:border-zinc-600 dark:bg-zinc-800"
|
||||
className="mb-2 w-full rounded-lg border border-zinc-300 px-2 py-1 text-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 dark:border-zinc-600 dark:bg-zinc-900"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Name des Plans"
|
||||
@@ -263,11 +270,11 @@ function NewPlanPopover({ onCreate, onClose }: { onCreate: (name: string) => voi
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onCreate(name)}
|
||||
className="rounded-md bg-zinc-900 px-2 py-1 text-xs text-white dark:bg-zinc-100 dark:text-zinc-900"
|
||||
className="rounded-lg bg-indigo-600 px-2 py-1 text-xs font-medium text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
||||
>
|
||||
Erstellen
|
||||
</button>
|
||||
<button type="button" onClick={onClose} className="text-xs text-zinc-500">
|
||||
<button type="button" onClick={onClose} className="text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300">
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
@@ -287,16 +294,16 @@ function ScenarioPopover({
|
||||
const [name, setName] = useState("Neues Szenario");
|
||||
const [branchFromPhaseId, setBranchFromPhaseId] = useState(phases[phases.length - 1]?.id ?? "");
|
||||
return (
|
||||
<div className="absolute left-0 top-10 z-10 w-72 rounded-md border border-zinc-200 bg-white p-3 shadow-lg dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="absolute left-0 top-10 z-10 w-72 rounded-lg border border-zinc-200 bg-white p-3 shadow-lg dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<input
|
||||
className="mb-2 w-full rounded-md border border-zinc-300 px-2 py-1 text-sm dark:border-zinc-600 dark:bg-zinc-800"
|
||||
className="mb-2 w-full rounded-lg border border-zinc-300 px-2 py-1 text-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 dark:border-zinc-600 dark:bg-zinc-900"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Name des Szenarios"
|
||||
/>
|
||||
<label className="mb-1 block text-xs text-zinc-500">Verzweigen ab Phase</label>
|
||||
<select
|
||||
className="mb-2 w-full rounded-md border border-zinc-300 px-2 py-1 text-sm dark:border-zinc-600 dark:bg-zinc-800"
|
||||
className="mb-2 w-full rounded-lg border border-zinc-300 px-2 py-1 text-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 dark:border-zinc-600 dark:bg-zinc-900"
|
||||
value={branchFromPhaseId}
|
||||
onChange={(e) => setBranchFromPhaseId(e.target.value)}
|
||||
>
|
||||
@@ -310,11 +317,11 @@ function ScenarioPopover({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onCreate(name, branchFromPhaseId)}
|
||||
className="rounded-md bg-zinc-900 px-2 py-1 text-xs text-white dark:bg-zinc-100 dark:text-zinc-900"
|
||||
className="rounded-lg bg-indigo-600 px-2 py-1 text-xs font-medium text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
||||
>
|
||||
Erstellen
|
||||
</button>
|
||||
<button type="button" onClick={onClose} className="text-xs text-zinc-500">
|
||||
<button type="button" onClick={onClose} className="text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300">
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { Bar, BarChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
||||
import { BarChart3, Download, LineChart as LineChartIcon } from "lucide-react";
|
||||
import { WealthChart, type TimelineSeries } from "@/components/WealthChart";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { formatChf } from "@/lib/format";
|
||||
import type { PlanComputed } from "@/lib/calculations";
|
||||
import type { PlanInput } from "@/lib/types";
|
||||
|
||||
const PALETTE = ["#3f3f46", "#2563eb", "#16a34a", "#d97706", "#dc2626", "#7c3aed"];
|
||||
const PALETTE = ["#4f46e5", "#0ea5e9", "#16a34a", "#d97706", "#dc2626", "#7c3aed"];
|
||||
|
||||
interface PlanListItem {
|
||||
id: string;
|
||||
@@ -80,13 +81,17 @@ export function Dashboard({
|
||||
<StatCard label="Geschaetzter Nachlass" value={computed.nachlass} help="Endvermoegen der letzten Phase - potenziell vererbbar." />
|
||||
</div>
|
||||
|
||||
<section className="rounded-lg border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<section className="rounded-xl border border-zinc-200/70 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
|
||||
<h3 className="text-sm font-semibold text-zinc-800 dark:text-zinc-100">Vermoegensverlauf</h3>
|
||||
<h3 className="flex items-center gap-1.5 text-sm font-semibold text-zinc-800 dark:text-zinc-100">
|
||||
<LineChartIcon className="h-4 w-4 text-indigo-500 dark:text-indigo-400" />
|
||||
Vermoegensverlauf
|
||||
</h3>
|
||||
<a
|
||||
href={`/api/plans/${plan.id}/export`}
|
||||
className="rounded-md border border-zinc-300 px-3 py-1.5 text-xs font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:text-zinc-200 dark:hover:bg-zinc-800"
|
||||
className="flex items-center gap-1.5 rounded-lg border border-zinc-300 px-3 py-1.5 text-xs font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-200 dark:hover:bg-zinc-800"
|
||||
>
|
||||
<Download className="h-3.5 w-3.5" />
|
||||
CSV-Export
|
||||
</a>
|
||||
</div>
|
||||
@@ -108,8 +113,9 @@ export function Dashboard({
|
||||
<WealthChart series={series} />
|
||||
</section>
|
||||
|
||||
<section className="rounded-lg border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<h3 className="mb-3 text-sm font-semibold text-zinc-800 dark:text-zinc-100">
|
||||
<section className="rounded-xl border border-zinc-200/70 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<h3 className="mb-3 flex items-center gap-1.5 text-sm font-semibold text-zinc-800 dark:text-zinc-100">
|
||||
<BarChart3 className="h-4 w-4 text-indigo-500 dark:text-indigo-400" />
|
||||
Vermoegensaufteilung pro Phase (Endvermoegen)
|
||||
</h3>
|
||||
<div className="h-72 w-full">
|
||||
@@ -136,11 +142,11 @@ export function Dashboard({
|
||||
|
||||
function StatCard({ label, value, help }: { label: string; value: number; help?: string }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="rounded-xl border border-zinc-200/70 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<div className="text-xs text-zinc-500 dark:text-zinc-400" title={help}>
|
||||
{label}
|
||||
</div>
|
||||
<div className="mt-1 text-xl font-semibold text-zinc-900 dark:text-zinc-50">
|
||||
<div className="mt-1 text-xl font-semibold text-indigo-600 dark:text-indigo-400">
|
||||
{formatChf(value)} CHF
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { InfoBubble } from "@/components/InfoBubble";
|
||||
import { floorToThousand, formatChf, parseChfInput } from "@/lib/format";
|
||||
|
||||
const baseInputClass =
|
||||
"w-full rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-sm text-zinc-900 focus:border-zinc-500 focus:outline-none dark:border-zinc-600 dark:bg-zinc-900 dark:text-zinc-100";
|
||||
"w-full rounded-lg border border-zinc-300 bg-white px-2.5 py-1.5 text-sm text-zinc-900 shadow-sm transition-colors focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100";
|
||||
|
||||
export function FieldLabel({ label, help }: { label: string; help?: string }) {
|
||||
return (
|
||||
@@ -114,7 +115,7 @@ export function MoneyInput({
|
||||
onChange(floorToThousand(parseChfInput(text)));
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex w-5 flex-col overflow-hidden rounded-r-md border-l border-zinc-300 dark:border-zinc-600">
|
||||
<div className="absolute inset-y-0 right-0 flex w-6 flex-col overflow-hidden rounded-r-lg border-l border-zinc-300 dark:border-zinc-700">
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
@@ -130,9 +131,9 @@ export function MoneyInput({
|
||||
startHold(1000);
|
||||
}}
|
||||
onTouchEnd={stopHold}
|
||||
className="flex flex-1 items-center justify-center text-[8px] leading-none text-zinc-500 hover:bg-zinc-100 dark:text-zinc-400 dark:hover:bg-zinc-800"
|
||||
className="flex flex-1 items-center justify-center text-zinc-500 hover:bg-indigo-50 hover:text-indigo-600 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:hover:text-indigo-400"
|
||||
>
|
||||
▲
|
||||
<ChevronUp className="h-3 w-3" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -149,9 +150,9 @@ export function MoneyInput({
|
||||
startHold(-1000);
|
||||
}}
|
||||
onTouchEnd={stopHold}
|
||||
className="flex flex-1 items-center justify-center border-t border-zinc-300 text-[8px] leading-none text-zinc-500 hover:bg-zinc-100 dark:border-zinc-600 dark:text-zinc-400 dark:hover:bg-zinc-800"
|
||||
className="flex flex-1 items-center justify-center border-t border-zinc-300 text-zinc-500 hover:bg-indigo-50 hover:text-indigo-600 dark:border-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:hover:text-indigo-400"
|
||||
>
|
||||
▼
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,7 +50,7 @@ export function HouseholdSettings({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 rounded-lg border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="flex flex-col gap-4 rounded-xl border border-zinc-200/70 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<SelectField
|
||||
label="Haushaltsform"
|
||||
value={householdType}
|
||||
@@ -88,14 +88,14 @@ export function HouseholdSettings({
|
||||
type="button"
|
||||
disabled={saving}
|
||||
onClick={handleSubmit}
|
||||
className="rounded-md bg-zinc-900 px-4 py-2 text-sm font-medium text-white hover:bg-zinc-700 disabled:opacity-50 dark:bg-zinc-100 dark:text-zinc-900"
|
||||
className="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-500 disabled:opacity-50 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
||||
>
|
||||
{saving ? "Speichern..." : "Speichern"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="rounded-md border border-zinc-300 px-4 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:text-zinc-200"
|
||||
className="rounded-lg border border-zinc-300 px-4 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-200 dark:hover:bg-zinc-800"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Info } from "lucide-react";
|
||||
|
||||
export function InfoBubble({ text }: { text: string }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -13,12 +14,12 @@ export function InfoBubble({ text }: { text: string }) {
|
||||
onMouseEnter={() => setOpen(true)}
|
||||
onMouseLeave={() => setOpen(false)}
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
className="flex h-4 w-4 items-center justify-center rounded-full bg-zinc-200 text-[10px] font-semibold text-zinc-600 hover:bg-zinc-300 dark:bg-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-600"
|
||||
className="flex h-4 w-4 items-center justify-center rounded-full bg-indigo-100 text-indigo-500 hover:bg-indigo-200 dark:bg-indigo-500/20 dark:text-indigo-300 dark:hover:bg-indigo-500/30"
|
||||
>
|
||||
i
|
||||
<Info className="h-2.5 w-2.5" strokeWidth={2.5} />
|
||||
</button>
|
||||
{open && (
|
||||
<span className="absolute left-1/2 top-6 z-20 w-64 -translate-x-1/2 rounded-md border border-zinc-200 bg-white p-2 text-xs leading-snug text-zinc-700 shadow-lg dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-200">
|
||||
<span className="absolute left-1/2 top-6 z-20 w-64 -translate-x-1/2 rounded-lg border border-zinc-200 bg-white p-2.5 text-xs leading-snug text-zinc-700 shadow-lg dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-200">
|
||||
{text}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { PiggyBank } from "lucide-react";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { NumberField, SelectField } from "@/components/FormField";
|
||||
import type { HouseholdInput, HouseholdType, PersonRole } from "@/lib/types";
|
||||
@@ -52,14 +53,17 @@ export function Onboarding({ onDone }: { onDone: (household: HouseholdInput) =>
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-xl flex-1 flex-col justify-center px-6 py-16">
|
||||
<h1 className="mb-2 text-2xl font-semibold text-zinc-900 dark:text-zinc-50">
|
||||
Willkommen beim Financial Planning Tool
|
||||
</h1>
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<PiggyBank className="h-7 w-7 text-indigo-600 dark:text-indigo-400" />
|
||||
<h1 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-50">
|
||||
Willkommen beim Financial Planning Tool
|
||||
</h1>
|
||||
</div>
|
||||
<p className="mb-8 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
Bevor es losgeht, brauchen wir ein paar Eckdaten zu Ihrem Haushalt.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-5 rounded-lg border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="flex flex-col gap-5 rounded-xl border border-zinc-200/70 bg-white p-6 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<SelectField
|
||||
label="Haushaltsform"
|
||||
help="Waehlen Sie, ob Sie alleine oder gemeinsam mit einer Partnerin / einem Partner planen."
|
||||
@@ -72,7 +76,7 @@ export function Onboarding({ onDone }: { onDone: (household: HouseholdInput) =>
|
||||
/>
|
||||
|
||||
{persons.map((person, index) => (
|
||||
<div key={person.role} className="grid grid-cols-2 gap-3 rounded-md bg-zinc-50 p-3 dark:bg-zinc-800/50">
|
||||
<div key={person.role} className="grid grid-cols-2 gap-3 rounded-xl border border-zinc-100 bg-zinc-50/60 p-3 dark:border-zinc-800 dark:bg-zinc-800/30">
|
||||
<div className="col-span-2 text-xs font-semibold uppercase tracking-wide text-zinc-500">
|
||||
{householdType === "COUPLE" ? (person.role === "PERSON_A" ? "Person A" : "Person B") : "Ihre Angaben"}
|
||||
</div>
|
||||
@@ -105,7 +109,7 @@ export function Onboarding({ onDone }: { onDone: (household: HouseholdInput) =>
|
||||
type="button"
|
||||
disabled={saving}
|
||||
onClick={handleSubmit}
|
||||
className="mt-2 rounded-md bg-zinc-900 px-4 py-2 text-sm font-medium text-white hover:bg-zinc-700 disabled:opacity-50 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-300"
|
||||
className="mt-2 rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-500 disabled:opacity-50 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
||||
>
|
||||
{saving ? "Speichern..." : "Weiter"}
|
||||
</button>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { LineChart, Line, ResponsiveContainer } from "recharts";
|
||||
import { AlertTriangle, ChevronDown, ChevronRight, Trash2 } from "lucide-react";
|
||||
import { PhaseForm } from "@/components/PhaseForm";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { formatChf } from "@/lib/format";
|
||||
@@ -43,19 +44,23 @@ export function PhaseCard({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-lg border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="overflow-hidden rounded-xl border border-zinc-200/70 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
className="flex w-full items-center gap-4 px-4 py-3 text-left hover:bg-zinc-50 dark:hover:bg-zinc-800/50"
|
||||
>
|
||||
<span className="text-zinc-400">{expanded ? "▾" : "▸"}</span>
|
||||
<span className="text-indigo-500 dark:text-indigo-400">
|
||||
{expanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="font-medium text-zinc-900 dark:text-zinc-100">{phase.name}</span>
|
||||
<span className="text-xs text-zinc-500">{phase.durationYears} Jahre</span>
|
||||
{computed.savingsWarning && (
|
||||
<span className="text-xs text-amber-600 dark:text-amber-400">⚠ Sparquote ueberschritten</span>
|
||||
<span className="flex items-center gap-1 text-xs text-amber-600 dark:text-amber-400">
|
||||
<AlertTriangle className="h-3 w-3" /> Sparquote ueberschritten
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-zinc-500 dark:text-zinc-400">
|
||||
@@ -65,7 +70,7 @@ export function PhaseCard({
|
||||
<div className="h-8 w-24">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={sparklineData}>
|
||||
<Line type="monotone" dataKey="value" stroke="#3f3f46" strokeWidth={1.5} dot={false} />
|
||||
<Line type="monotone" dataKey="value" stroke="#4f46e5" strokeWidth={1.5} dot={false} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
@@ -77,8 +82,9 @@ export function PhaseCard({
|
||||
e.stopPropagation();
|
||||
handleDelete();
|
||||
}}
|
||||
className="rounded-md border border-zinc-300 px-2 py-1 text-xs text-zinc-500 hover:bg-red-50 hover:text-red-600 dark:border-zinc-600 dark:hover:bg-red-950"
|
||||
className="flex items-center gap-1 rounded-lg border border-zinc-300 px-2 py-1 text-xs text-zinc-500 hover:border-red-200 hover:bg-red-50 hover:text-red-600 dark:border-zinc-700 dark:hover:border-red-500/30 dark:hover:bg-red-950"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
{deleting ? "…" : "Loeschen"}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import {
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
Gift,
|
||||
Home,
|
||||
Plus,
|
||||
PiggyBank,
|
||||
ShoppingCart,
|
||||
TrendingUp,
|
||||
Wallet,
|
||||
X,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import { MoneyField, NumberField, SelectField, TextField } from "@/components/FormField";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { formatChf } from "@/lib/format";
|
||||
@@ -147,7 +160,7 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
</section>
|
||||
|
||||
{/* Einkommen */}
|
||||
<Section title="Einkommen">
|
||||
<Section title="Einkommen" icon={<Wallet className="h-4 w-4" />}>
|
||||
{incomeEntries.map((entry, i) => (
|
||||
<div key={entry.id} className="grid grid-cols-[1fr_1fr_auto] items-end gap-2">
|
||||
{incomeMode === "PER_PERSON" ? (
|
||||
@@ -183,7 +196,7 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
</div>
|
||||
))}
|
||||
<AddButton
|
||||
label="+ Einkommensposten"
|
||||
label="Einkommensposten"
|
||||
onClick={() =>
|
||||
setIncomeEntries((prev) => [
|
||||
...prev,
|
||||
@@ -194,7 +207,7 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
</Section>
|
||||
|
||||
{/* Ausgaben */}
|
||||
<Section title="Ausgaben">
|
||||
<Section title="Ausgaben" icon={<ShoppingCart className="h-4 w-4" />}>
|
||||
{expenseEntries.map((entry, i) => (
|
||||
<div key={entry.id} className="grid grid-cols-[1fr_auto] items-end gap-2">
|
||||
<MoneyField
|
||||
@@ -209,19 +222,19 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
</div>
|
||||
))}
|
||||
<AddButton
|
||||
label="+ Ausgabenposten"
|
||||
label="Ausgabenposten"
|
||||
onClick={() => setExpenseEntries((prev) => [...prev, { id: tempId(), label: null, amount: 0 }])}
|
||||
/>
|
||||
<div className="rounded-md bg-zinc-50 px-3 py-2 text-sm text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300">
|
||||
<div className="rounded-xl bg-indigo-50/60 px-3 py-2 text-sm text-zinc-600 dark:bg-indigo-500/10 dark:text-zinc-300">
|
||||
Verfuegbare Sparquote (CHF/Jahr): <strong>{formatChf(savingsQuota)}</strong>
|
||||
{" "}(Details und Verteilung siehe Wertschriften weiter unten)
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Wertschriften */}
|
||||
<Section title="Wertschriften">
|
||||
<Section title="Wertschriften" icon={<TrendingUp className="h-4 w-4" />}>
|
||||
{securities.map((s, i) => (
|
||||
<div key={s.id} className="grid grid-cols-2 gap-2 rounded-md bg-zinc-50 p-3 sm:grid-cols-5 dark:bg-zinc-800/50">
|
||||
<div key={s.id} className="grid grid-cols-2 gap-2 rounded-xl border border-zinc-100 bg-zinc-50/60 p-3 sm:grid-cols-5 dark:border-zinc-800 dark:bg-zinc-800/30">
|
||||
<TextField
|
||||
label="Name"
|
||||
help="Frei waehlbare Bezeichnung, z. B. 'Globaler ETF' oder 'Schweizer Aktien'."
|
||||
@@ -266,7 +279,7 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
</div>
|
||||
))}
|
||||
<AddButton
|
||||
label="+ Wertschrift"
|
||||
label="Wertschrift"
|
||||
onClick={() =>
|
||||
setSecurities((prev) => [
|
||||
...prev,
|
||||
@@ -283,7 +296,7 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
])
|
||||
}
|
||||
/>
|
||||
<div className="flex flex-col gap-1.5 rounded-md bg-zinc-50 px-3 py-2 text-sm dark:bg-zinc-800">
|
||||
<div className="flex flex-col gap-1.5 rounded-xl bg-indigo-50/60 px-3 py-2 text-sm dark:bg-indigo-500/10">
|
||||
<div className="flex items-center gap-2">
|
||||
<StatusDot ok={!startCapitalRemaining} />
|
||||
Verfuegbares Startkapital (aus Verkaeufen der Vorphase): <strong>{formatChf(phase.incomingCapital)}</strong> CHF
|
||||
@@ -295,17 +308,18 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
{" "}— zugewiesen: {formatChf(allocated)}
|
||||
</div>
|
||||
{overAllocated && (
|
||||
<div className="text-amber-700 dark:text-amber-400">
|
||||
⚠ Die zugewiesenen Sparbeitraege uebersteigen die verfuegbare Sparquote.
|
||||
<div className="flex items-center gap-1.5 text-amber-700 dark:text-amber-400">
|
||||
<AlertTriangle className="h-3.5 w-3.5 shrink-0" />
|
||||
Die zugewiesenen Sparbeitraege uebersteigen die verfuegbare Sparquote.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Immobilien */}
|
||||
<Section title="Immobilien">
|
||||
<Section title="Immobilien" icon={<Home className="h-4 w-4" />}>
|
||||
{realEstates.map((re, i) => (
|
||||
<div key={re.id} className="grid grid-cols-2 gap-2 rounded-md bg-zinc-50 p-3 sm:grid-cols-4 dark:bg-zinc-800/50">
|
||||
<div key={re.id} className="grid grid-cols-2 gap-2 rounded-xl border border-zinc-100 bg-zinc-50/60 p-3 sm:grid-cols-4 dark:border-zinc-800 dark:bg-zinc-800/30">
|
||||
<TextField
|
||||
label="Bezeichnung"
|
||||
help="Z. B. 'Eigenheim' oder 'Ferienwohnung'."
|
||||
@@ -336,7 +350,7 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
</div>
|
||||
))}
|
||||
<AddButton
|
||||
label="+ Immobilie"
|
||||
label="Immobilie"
|
||||
onClick={() =>
|
||||
setRealEstates((prev) => [
|
||||
...prev,
|
||||
@@ -347,7 +361,7 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
</Section>
|
||||
|
||||
{/* Sondereinnahmen/-ausgaben */}
|
||||
<Section title="Sondereinnahmen / -ausgaben">
|
||||
<Section title="Sondereinnahmen / -ausgaben" icon={<Gift className="h-4 w-4" />}>
|
||||
{oneTimeEvents.map((ev, i) => (
|
||||
<div key={ev.id} className="grid grid-cols-[auto_1fr_2fr_auto] items-end gap-2">
|
||||
<SelectField
|
||||
@@ -374,15 +388,15 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
</div>
|
||||
))}
|
||||
<AddButton
|
||||
label="+ Sondereinnahme/-ausgabe"
|
||||
label="Sondereinnahme/-ausgabe"
|
||||
onClick={() => setOneTimeEvents((prev) => [...prev, { id: tempId(), type: "EXPENSE", amount: 0, description: null }])}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
{/* Pensionierung */}
|
||||
<Section title="Pensionierung (optional)">
|
||||
<Section title="Pensionierung (optional)" icon={<PiggyBank className="h-4 w-4" />}>
|
||||
{retirementInfos.map((r, i) => (
|
||||
<div key={r.id} className="grid grid-cols-2 gap-2 rounded-md bg-zinc-50 p-3 sm:grid-cols-4 dark:bg-zinc-800/50">
|
||||
<div key={r.id} className="grid grid-cols-2 gap-2 rounded-xl border border-zinc-100 bg-zinc-50/60 p-3 sm:grid-cols-4 dark:border-zinc-800 dark:bg-zinc-800/30">
|
||||
<SelectField
|
||||
label="Person"
|
||||
value={r.personId}
|
||||
@@ -419,7 +433,7 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
</div>
|
||||
))}
|
||||
<AddButton
|
||||
label="+ Pensionierungsangaben"
|
||||
label="Pensionierungsangaben"
|
||||
onClick={() =>
|
||||
setRetirementInfos((prev) => [
|
||||
...prev,
|
||||
@@ -436,21 +450,26 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
/>
|
||||
</Section>
|
||||
|
||||
{error && <p className="text-sm text-red-600 dark:text-red-400">{error}</p>}
|
||||
{error && (
|
||||
<p className="flex items-center gap-1.5 text-sm text-red-600 dark:text-red-400">
|
||||
<AlertTriangle className="h-4 w-4 shrink-0" />
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
disabled={saving}
|
||||
onClick={handleSave}
|
||||
className="rounded-md bg-zinc-900 px-4 py-2 text-sm font-medium text-white hover:bg-zinc-700 disabled:opacity-50 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-300"
|
||||
className="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-500 disabled:opacity-50 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
||||
>
|
||||
{saving ? "Speichern..." : "Speichern"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="rounded-md border border-zinc-300 px-4 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:text-zinc-200 dark:hover:bg-zinc-800"
|
||||
className="rounded-lg border border-zinc-300 px-4 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-200 dark:hover:bg-zinc-800"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
@@ -459,10 +478,21 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
function Section({
|
||||
title,
|
||||
icon,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
icon: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="flex flex-col gap-2">
|
||||
<h4 className="text-sm font-semibold text-zinc-800 dark:text-zinc-100">{title}</h4>
|
||||
<h4 className="flex items-center gap-1.5 text-sm font-semibold text-zinc-800 dark:text-zinc-100">
|
||||
<span className="text-indigo-500 dark:text-indigo-400">{icon}</span>
|
||||
{title}
|
||||
</h4>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
@@ -473,19 +503,22 @@ function AddButton({ label, onClick }: { label: string; onClick: () => void }) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="self-start text-xs font-medium text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
|
||||
className="flex items-center gap-1 self-start text-xs font-medium text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusDot({ ok }: { ok: boolean }) {
|
||||
return (
|
||||
<span
|
||||
title={ok ? "Vollstaendig verteilt" : "Noch nicht vollstaendig verteilt"}
|
||||
className={`inline-block h-2.5 w-2.5 shrink-0 rounded-full ${ok ? "bg-emerald-500" : "bg-red-500"}`}
|
||||
return ok ? (
|
||||
<CheckCircle2
|
||||
className="h-4 w-4 shrink-0 text-emerald-500"
|
||||
aria-label="Vollstaendig verteilt"
|
||||
/>
|
||||
) : (
|
||||
<XCircle className="h-4 w-4 shrink-0 text-red-500" aria-label="Noch nicht vollstaendig verteilt" />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -495,9 +528,9 @@ function RemoveButton({ onClick }: { onClick: () => void }) {
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
aria-label="Entfernen"
|
||||
className="rounded-md border border-zinc-300 px-2 py-1.5 text-xs text-zinc-500 hover:bg-red-50 hover:text-red-600 dark:border-zinc-600 dark:hover:bg-red-950"
|
||||
className="rounded-lg border border-zinc-300 px-2 py-1.5 text-zinc-500 hover:border-red-200 hover:bg-red-50 hover:text-red-600 dark:border-zinc-700 dark:hover:border-red-500/30 dark:hover:bg-red-950"
|
||||
>
|
||||
✕
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { ArrowDown, CheckCircle2 } from "lucide-react";
|
||||
import { MoneyInput } from "@/components/FormField";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { floorToThousand, formatChf } from "@/lib/format";
|
||||
@@ -116,7 +117,7 @@ export function TransitionPanel({
|
||||
|
||||
if (!loaded) {
|
||||
return (
|
||||
<div className="mx-2 rounded-md bg-zinc-100 px-4 py-3 text-xs text-zinc-500 dark:bg-zinc-800">
|
||||
<div className="mx-2 rounded-xl bg-zinc-100 px-4 py-3 text-xs text-zinc-500 dark:bg-zinc-800">
|
||||
Uebergang wird geladen…
|
||||
</div>
|
||||
);
|
||||
@@ -166,8 +167,9 @@ export function TransitionPanel({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-2 flex flex-col gap-3 rounded-md border border-dashed border-zinc-300 bg-zinc-50 p-4 dark:border-zinc-600 dark:bg-zinc-800/40">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-zinc-500">
|
||||
<div className="mx-2 flex flex-col gap-3 rounded-xl border border-dashed border-indigo-200 bg-indigo-50/40 p-4 dark:border-indigo-500/30 dark:bg-indigo-500/5">
|
||||
<div className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-indigo-600 dark:text-indigo-400">
|
||||
<ArrowDown className="h-3.5 w-3.5" />
|
||||
Uebergang → {nextPhaseName}
|
||||
</div>
|
||||
<table className="w-full text-sm">
|
||||
@@ -181,11 +183,11 @@ export function TransitionPanel({
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((it, i) => (
|
||||
<tr key={`${it.positionType}-${it.id}`} className="border-t border-zinc-200 dark:border-zinc-700">
|
||||
<tr key={`${it.positionType}-${it.id}`} className="border-t border-zinc-200 dark:border-zinc-800">
|
||||
<td className="py-2 pr-2">{it.name}</td>
|
||||
<td className="py-2 pr-2">
|
||||
<select
|
||||
className="rounded-md border border-zinc-300 bg-white px-2 py-1 text-xs dark:border-zinc-600 dark:bg-zinc-900"
|
||||
className="rounded-lg border border-zinc-300 bg-white px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 dark:border-zinc-700 dark:bg-zinc-900"
|
||||
value={it.decision}
|
||||
onChange={(e) =>
|
||||
setItems((prev) =>
|
||||
@@ -218,7 +220,7 @@ export function TransitionPanel({
|
||||
{it.positionType === "REAL_ESTATE" && it.decision === "SELL" ? (
|
||||
<input
|
||||
type="number"
|
||||
className="w-20 rounded-md border border-zinc-300 bg-white px-2 py-1 text-xs dark:border-zinc-600 dark:bg-zinc-900"
|
||||
className="w-20 rounded-lg border border-zinc-300 bg-white px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 dark:border-zinc-700 dark:bg-zinc-900"
|
||||
value={it.saleTaxRate}
|
||||
onChange={(e) =>
|
||||
setItems((prev) =>
|
||||
@@ -234,7 +236,7 @@ export function TransitionPanel({
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="rounded-md bg-white px-3 py-2 text-sm dark:bg-zinc-900">
|
||||
<div className="rounded-xl bg-white px-3 py-2 text-sm shadow-sm dark:bg-zinc-900">
|
||||
Verfuegbares Startkapital fuer neue Phase (aus Verkaeufen): <strong>{formatChf(totalAvailableCapital)} CHF</strong>
|
||||
<p className="mt-1 text-xs text-zinc-500">
|
||||
Wird beim Speichern automatisch in "{nextPhaseName}" als verfuegbares Startkapital hinterlegt.
|
||||
@@ -248,11 +250,15 @@ export function TransitionPanel({
|
||||
type="button"
|
||||
disabled={saving}
|
||||
onClick={handleSave}
|
||||
className="self-start rounded-md bg-zinc-900 px-3 py-1.5 text-xs font-medium text-white hover:bg-zinc-700 disabled:opacity-50 dark:bg-zinc-100 dark:text-zinc-900"
|
||||
className="self-start rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-medium text-white shadow-sm hover:bg-indigo-500 disabled:opacity-50 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
||||
>
|
||||
{saving ? "Speichern..." : "Uebergang speichern"}
|
||||
</button>
|
||||
{saved && <span className="text-xs text-emerald-600 dark:text-emerald-400">Gespeichert.</span>}
|
||||
{saved && (
|
||||
<span className="flex items-center gap-1 text-xs text-emerald-600 dark:text-emerald-400">
|
||||
<CheckCircle2 className="h-3.5 w-3.5" /> Gespeichert.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user