diff --git a/src/features/planner/PlannerView.tsx b/src/features/planner/PlannerView.tsx index 625eb5e..748de30 100644 --- a/src/features/planner/PlannerView.tsx +++ b/src/features/planner/PlannerView.tsx @@ -21,7 +21,7 @@ import { ContentDialogFooter, Skeleton, } from "@seed-design/react"; -import { Card, MercuryButton, HideAmountsToggle, NameEdit } from "@/ds"; +import { Card, MercuryButton, HideAmountsToggle, NameEdit, IconChip, SectionHeader, EmptyState } from "@/ds"; import { categoryStyle } from "@/ds/categoryStyle"; import { Icon } from "@/ds/icons"; import { tugrik, tugrikShort } from "@/ds/money"; @@ -35,6 +35,12 @@ type CategoryLimit = Budget["categories"][number]; type HorizonReport = Budget["report"]["day"]; type LimitRow = HorizonReport["rows"][number]; +// Savings goals aren't a transaction category, so they get their own fixed +// chip color rather than borrowing `categoryStyle` (which maps backend +// category names → Mongolian display + color). +const SAVINGS_TINT = "#DDEAF6"; +const SAVINGS_FG = "#215C9A"; + function dec(v: string | undefined | null): number { return parseFloat(v ?? "0") || 0; } @@ -43,6 +49,15 @@ function onlyDigits(v: string): string { return v.replace(/[^0-9]/g, ""); } +/** Splits a "Title — hint" string (the shape every empty-state copy in + * strings.ts uses) into its two halves for `EmptyState`, without inventing + * new copy. Falls back to the whole string as the title if there's no dash. */ +function splitEmptyCopy(text: string): { title: string; hint?: string } { + const idx = text.indexOf(" — "); + if (idx === -1) return { title: text }; + return { title: text.slice(0, idx), hint: text.slice(idx + 3) }; +} + const HORIZON_LABEL: Record = { day: s.horizon.day, week: s.horizon.week, @@ -245,10 +260,18 @@ function PlannedIncomeCard({ className="flex w-full flex-col items-start gap-3 text-left" style={{ background: "none", border: "none", padding: 0, cursor: "pointer" }} > - {s.plannedIncome.label} - +{tugrikShort(plannedIncome)} +
+ + {s.plannedIncome.label} + + +
+ +{tugrikShort(plannedIncome)} {showBreakdown && ( -
+
{loanObligations > 0 && ( )} @@ -293,6 +316,7 @@ function OverallCard({ const [editing, setEditing] = React.useState(false); const [draft, setDraft] = React.useState(""); const percent = limit > 0 ? Math.min(100, (spent / limit) * 100) : 0; + const limitSet = limit > 0; return (
- - - - -
- {OVERALL_LABEL[horizon]} - - {tugrikShort(spent)} - {limit > 0 ? ` / ${tugrikShort(limit)}` : " / —"} - +
+ + + + +
+ {OVERALL_LABEL[horizon]} + + {tugrikShort(spent)} + {limitSet ? ` / ${tugrikShort(limit)}` : ""} + + {!limitSet && {s.overall.unsetHint}} +
+ )}
@@ -377,6 +405,8 @@ function CategoryList({ const [newName, setNewName] = React.useState(""); const [confirmRemove, setConfirmRemove] = React.useState(null); + const empty = React.useMemo(() => splitEmptyCopy(s.categories.empty), []); + function openEditor(name: string) { const limit = categoryLimitFor(name); setDrafts({ day: dec(limit.day) > 0 ? limit.day : "", week: dec(limit.week) > 0 ? limit.week : "", month: dec(limit.month) > 0 ? limit.month : "" }); @@ -385,12 +415,14 @@ function CategoryList({ return ( -
-

{s.categories.title}

- { setNewName(""); setAdding(true); }}> - {s.categories.add} - -
+ { + setNewName(""); + setAdding(true); + }} + actionLabel={s.categories.add} + /> {adding && (
@@ -425,9 +457,7 @@ function CategoryList({ )} {!loading && rows.length === 0 && ( -

- {s.categories.empty} -

+ )} {!loading && rows.length > 0 && ( @@ -437,6 +467,7 @@ function CategoryList({ const limit = dec(row.limit); const over = limit > 0 && spent > limit; const percent = limit > 0 ? Math.min(100, (spent / limit) * 100) : 0; + const style = categoryStyle(row.category); if (editingName === row.category) { return ( @@ -472,47 +503,59 @@ function CategoryList({ } return ( -
  • -
    - -
    - - - - - {categoryStyle(row.category).name} +
  • + + +
    +
    + + {style.name} - + {tugrik(spent)} {limit > 0 ? ` / ${tugrik(limit)}` : " / —"}
    - - -
    +
  • + + ); })} @@ -587,20 +630,13 @@ function SavingsSection({ const [editing, setEditing] = React.useState<{ mode: "add" | "edit"; goal?: SavingsGoal } | null>(null); const [confirmDelete, setConfirmDelete] = React.useState(null); + const empty = React.useMemo(() => splitEmptyCopy(s.savings.empty), []); + return ( -
    -

    {s.savings.title}

    - setEditing({ mode: "add" })}> - {s.savings.add} - -
    + setEditing({ mode: "add" })} actionLabel={s.savings.add} /> - {goals.length === 0 && !editing && ( -

    - {s.savings.empty} -

    - )} + {goals.length === 0 && !editing && } {goals.length > 0 && (
      @@ -616,26 +652,46 @@ function SavingsSection({ ); diff --git a/src/features/planner/strings.ts b/src/features/planner/strings.ts index d9b1530..4228fc5 100644 --- a/src/features/planner/strings.ts +++ b/src/features/planner/strings.ts @@ -24,6 +24,7 @@ export const plannerStrings = { month: "Энэ сарын лимит", }, editTitle: (horizonLabel: string) => `${horizonLabel} — нийт лимит`, + unsetHint: "Лимит тохируулаагүй", }, categories: { title: "Ангиллын лимит",