feat(proj): init
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { formatDate, formatMoney } from "../api";
|
||||
import { useCabinet } from "../cabinet/CabinetContext";
|
||||
import { Banner, PageHeader } from "../components/ui";
|
||||
import {
|
||||
AppListSection,
|
||||
AppListTile,
|
||||
AppProgress,
|
||||
AppSkeleton,
|
||||
AppText,
|
||||
} from "../ui";
|
||||
|
||||
function SkeletonOverview() {
|
||||
return (
|
||||
<section aria-busy="true" aria-label="Загрузка">
|
||||
<AppSkeleton width="8rem" />
|
||||
<div style={{ height: 16 }} />
|
||||
<AppSkeleton width="16rem" height={34} />
|
||||
<div style={{ height: 20 }} />
|
||||
<AppListSection header="Сводка">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<AppListTile key={i} title={<AppSkeleton width="40%" />} value={<AppSkeleton width="4rem" />} />
|
||||
))}
|
||||
</AppListSection>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function OverviewPage() {
|
||||
const { status, loading, noBudget } = useCabinet();
|
||||
|
||||
if (loading && !status && !noBudget) {
|
||||
return <SkeletonOverview />;
|
||||
}
|
||||
|
||||
if (noBudget) {
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="Задай бюджет"
|
||||
description="Укажи сумму и дату зарплаты — посчитаем дневной лимит."
|
||||
/>
|
||||
<Link to="/budgets" className="app-btn app-btn--filled app-btn--large app-btn--expanded">
|
||||
Перейти к бюджетам
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!status) {
|
||||
return (
|
||||
<PageHeader
|
||||
title="Обзор"
|
||||
description="Не удалось загрузить статус. Обнови страницу или проверь API."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
eyebrow={`${status.budget.name} · ${formatDate(status.budget.start_date)} — ${formatDate(status.budget.end_date)} · ещё ${status.days_left} дн.${status.budget.is_active ? "" : " · выкл"}`}
|
||||
title="Обзор"
|
||||
description={
|
||||
<>
|
||||
Лимит на сегодня {formatMoney(status.daily_limit)}
|
||||
{status.spent_today > 0
|
||||
? ` · израсходовано ${formatMoney(status.spent_today)}`
|
||||
: ""}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<AppText variant="footnote">Остаток</AppText>
|
||||
<AppText variant="largeTitle" as="p">
|
||||
{formatMoney(status.remaining)}
|
||||
</AppText>
|
||||
|
||||
<div className="progress">
|
||||
<AppProgress
|
||||
spent={status.total_spent}
|
||||
total={status.budget.total_amount}
|
||||
label="Использование бюджета"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AppListSection header="Сводка">
|
||||
<AppListTile title="Бюджет" value={formatMoney(status.budget.total_amount)} />
|
||||
<AppListTile title="Потрачено" value={formatMoney(status.total_spent)} />
|
||||
<AppListTile title="Сегодня" value={formatMoney(status.spent_today)} />
|
||||
<AppListTile
|
||||
title="Ещё сегодня"
|
||||
value={
|
||||
<span className={status.remaining_today < 0 ? "is-warn" : undefined}>
|
||||
{formatMoney(status.remaining_today)}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</AppListSection>
|
||||
|
||||
{(status.is_over_budget || status.is_over_daily || status.is_expired) && (
|
||||
<Banner>
|
||||
{status.is_expired
|
||||
? "Период закончился — задай новый бюджет."
|
||||
: status.is_over_budget
|
||||
? "Бюджет превышен."
|
||||
: "Сегодняшний лимит превышен — завтра пересчитается."}
|
||||
</Banner>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user