Compare commits
No commits in common. "55b2bf61a5bf27ba6d20388d1f89fd6509942ecd" and "fdebc97c9a9d46685c1acbec514473ce7caf19b4" have entirely different histories.
55b2bf61a5
...
fdebc97c9a
3 changed files with 3 additions and 77 deletions
|
|
@ -4,8 +4,6 @@ export const MonthSchema = z.object({ month: z.string(), income: z.string(), exp
|
||||||
export const AnalyzeSchema = z.object({ from: z.string(), to: z.string(), income: z.string(), expense: z.string(),
|
export const AnalyzeSchema = z.object({ from: z.string(), to: z.string(), income: z.string(), expense: z.string(),
|
||||||
discretionaryExpense: z.string().nullish(), net: z.string(), months: z.array(MonthSchema).nullish(),
|
discretionaryExpense: z.string().nullish(), net: z.string(), months: z.array(MonthSchema).nullish(),
|
||||||
expenseCategories: z.array(NamedSchema).nullish(), incomePayees: z.array(NamedSchema).nullish(),
|
expenseCategories: z.array(NamedSchema).nullish(), incomePayees: z.array(NamedSchema).nullish(),
|
||||||
expensePayees: z.array(NamedSchema).nullish(),
|
expensePayees: z.array(NamedSchema).nullish() });
|
||||||
salaryIncome: z.string().nullish(), otherIncome: z.string().nullish(),
|
|
||||||
monthlySalary: z.string().nullish(), nextPayday: z.string().nullish() });
|
|
||||||
export type Named = z.infer<typeof NamedSchema>;
|
export type Named = z.infer<typeof NamedSchema>;
|
||||||
export type Analyze = z.infer<typeof AnalyzeSchema>;
|
export type Analyze = z.infer<typeof AnalyzeSchema>;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import {
|
||||||
useSubscriptions,
|
useSubscriptions,
|
||||||
useTransactions,
|
useTransactions,
|
||||||
} from "../../api/hooks/reads";
|
} from "../../api/hooks/reads";
|
||||||
import type { Named, Analyze } from "../../api/schemas/analyze";
|
import type { Named } from "../../api/schemas/analyze";
|
||||||
import type { Subscription } from "../../api/schemas/subscription";
|
import type { Subscription } from "../../api/schemas/subscription";
|
||||||
import { buildHome, dec, type HomeData } from "./buildHome";
|
import { buildHome, dec, type HomeData } from "./buildHome";
|
||||||
import { buildTrendMonths, buildCashFlowVerdict, type TrendMonth } from "./cashFlowTrend";
|
import { buildTrendMonths, buildCashFlowVerdict, type TrendMonth } from "./cashFlowTrend";
|
||||||
|
|
@ -232,65 +232,6 @@ function Dot({ color }: { color: string }) {
|
||||||
return <span aria-hidden style={{ display: "inline-block", width: 7, height: 7, borderRadius: 2, background: color, marginRight: 5 }} />;
|
return <span aria-hidden style={{ display: "inline-block", width: 7, height: 7, borderRadius: 2, background: color, marginRight: 5 }} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** One labelled income component (salary / support) with a leading colour dot. */
|
|
||||||
function IncomeRow({ label, value, dotColor, loading, hidden }: { label: string; value: number; dotColor: string; loading: boolean; hidden: boolean }) {
|
|
||||||
return (
|
|
||||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
||||||
<span style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 14 }}>
|
|
||||||
<span aria-hidden style={{ width: 10, height: 10, borderRadius: "50%", background: dotColor, flex: "none" }} />
|
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
<span style={{ fontSize: 15, fontWeight: 700 }}>
|
|
||||||
<Amount value={value} loading={loading} hidden={hidden} />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Income card: this month's earnings split into salary vs support/other, and
|
|
||||||
* how much was kept, so reliable pay reads apart from variable family support. */
|
|
||||||
function IncomeCard({ month, loading, hidden }: { month?: Analyze; loading: boolean; hidden: boolean }) {
|
|
||||||
const salary = dec(month?.salaryIncome);
|
|
||||||
const support = dec(month?.otherIncome);
|
|
||||||
const typical = dec(month?.monthlySalary);
|
|
||||||
const income = dec(month?.income);
|
|
||||||
const expense = dec(month?.expense);
|
|
||||||
const saved = income - expense;
|
|
||||||
const rate = income > 0 ? Math.round((saved / income) * 100) : 0;
|
|
||||||
const spentFrac = income > 0 ? Math.min(1, expense / income) : 0;
|
|
||||||
const over = saved < 0;
|
|
||||||
const good = "var(--mercury-success, #1e9e6b)";
|
|
||||||
const bad = "var(--mercury-critical, #e5484d)";
|
|
||||||
return (
|
|
||||||
<Card style={{ display: "flex", flexDirection: "column", gap: 16, padding: 20 }}>
|
|
||||||
<SectionHeader title={s.incomeTitle} />
|
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
|
|
||||||
<IncomeRow label={s.incomeSalary} value={salary} dotColor={good} loading={loading} hidden={hidden} />
|
|
||||||
{!loading && typical > 0 && (
|
|
||||||
<div style={{ fontSize: 12, color: "var(--seed-color-fg-neutral-muted, #8b8b8b)", marginTop: -6, marginLeft: 18 }}>
|
|
||||||
{s.incomeTypical} {hidden ? MASKED : tugrikShortRaw(typical)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<IncomeRow label={s.incomeSupport} value={support} dotColor="var(--mercury-brand-yellow, #f2b021)" loading={loading} hidden={hidden} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div style={{ height: 8, borderRadius: 999, background: "var(--seed-color-bg-neutral-subtle, #eef0f2)", overflow: "hidden" }}>
|
|
||||||
<div style={{ height: "100%", width: `${Math.max(2, spentFrac * 100)}%`, borderRadius: 999, background: over ? bad : good }} />
|
|
||||||
</div>
|
|
||||||
<div style={{ marginTop: 10, display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8 }}>
|
|
||||||
<span style={{ fontSize: 13, fontWeight: 700, color: over ? bad : good }}>
|
|
||||||
{over ? s.incomeOverspent : s.incomeSaveRate(rate)}
|
|
||||||
</span>
|
|
||||||
<span style={{ fontSize: 14, fontWeight: 700, color: over ? bad : good, flex: "none" }}>
|
|
||||||
{over ? "−" : "+"}
|
|
||||||
<Amount value={Math.abs(saved)} loading={loading} hidden={hidden} />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The Нүүр (home) "money clarity" dashboard: a slim categorize-nudge banner,
|
/** The Нүүр (home) "money clarity" dashboard: a slim categorize-nudge banner,
|
||||||
* the safe-to-spend + cash-flow hero, where-your-money-went, recurring &
|
* the safe-to-spend + cash-flow hero, where-your-money-went, recurring &
|
||||||
* subscriptions, and net worth + composition. Rebuilt per the mercury-rethink
|
* subscriptions, and net worth + composition. Rebuilt per the mercury-rethink
|
||||||
|
|
@ -463,10 +404,7 @@ export function DashboardView() {
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* 2. Income — salary vs support, and what was kept this month. */}
|
{/* 2. Хаана зарцуулсан бэ? — top categories + top merchants. */}
|
||||||
<IncomeCard month={monthQ.data} loading={heroLoading} hidden={hiddenAmounts} />
|
|
||||||
|
|
||||||
{/* 3. Хаана зарцуулсан бэ? — top categories + top merchants. */}
|
|
||||||
<Card style={{ display: "flex", flexDirection: "column", gap: 16, padding: 20 }}>
|
<Card style={{ display: "flex", flexDirection: "column", gap: 16, padding: 20 }}>
|
||||||
<SectionHeader title={s.whereWentTitle} />
|
<SectionHeader title={s.whereWentTitle} />
|
||||||
{whereItWentLoading ? (
|
{whereItWentLoading ? (
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,6 @@ export const homeStrings = {
|
||||||
// Shared "see the full list" link, capped sections on the home cards.
|
// Shared "see the full list" link, capped sections on the home cards.
|
||||||
viewAll: "Бүгдийг харах",
|
viewAll: "Бүгдийг харах",
|
||||||
|
|
||||||
// Income card — earned salary vs support, and what's kept.
|
|
||||||
incomeTitle: "Энэ сарын орлого",
|
|
||||||
incomeSalary: "Цалин",
|
|
||||||
incomeTypical: "сарын дундаж",
|
|
||||||
incomeSupport: "Дэмжлэг ба бусад",
|
|
||||||
incomeSaved: "Хэмнэсэн",
|
|
||||||
incomeSpentOf: "зарцуулсан",
|
|
||||||
incomeSaveRate: (pct: number) => `Орлогынхоо ${pct}%-г үлдээсэн`,
|
|
||||||
incomeOverspent: "Энэ сар орлогоосоо илүү зарцуулсан",
|
|
||||||
|
|
||||||
// 2. Хаана зарцуулсан бэ?
|
// 2. Хаана зарцуулсан бэ?
|
||||||
whereWentTitle: "Хаана зарцуулсан бэ?",
|
whereWentTitle: "Хаана зарцуулсан бэ?",
|
||||||
categoriesLabel: "Ангилал",
|
categoriesLabel: "Ангилал",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue