feat(pwa/repairs): show price per row + total sum header
В каждой карточке ленты ремонтов справа от строки работ выводим сумму работ (works_total из бэка, без запчастей). Шапка над лентой переехала: слева «Итого: NNN ₽ (X ремонтов)», справа кнопка «Обновить» (раньше была одна сверху-справа).
This commit is contained in:
@@ -14,6 +14,7 @@ export interface FeedItem {
|
|||||||
mechanic_name: string;
|
mechanic_name: string;
|
||||||
works_preview: FeedWorkBrief[];
|
works_preview: FeedWorkBrief[];
|
||||||
works_extra: number;
|
works_extra: number;
|
||||||
|
works_total: number; // сумма работ без запчастей
|
||||||
photo_url: string | null;
|
photo_url: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,8 +83,19 @@ export default function RepairsFeedPage() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="flex-1 container max-w-2xl py-3 space-y-2">
|
<main className="flex-1 container max-w-2xl py-3 space-y-2">
|
||||||
{/* Refresh tap-area для не-touch юзкейса; жест pull-to-refresh — в PR-8. */}
|
{/* Итого слева — сумма работ загруженных ремонтов; Обновить справа. */}
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-sm">
|
||||||
|
<span className="text-muted-foreground">Итого:</span>{" "}
|
||||||
|
<span className="font-semibold tabular-nums">
|
||||||
|
{fmtMoney(items.reduce((s, it) => s + (it.works_total || 0), 0))} ₽
|
||||||
|
</span>
|
||||||
|
{items.length > 0 && (
|
||||||
|
<span className="text-xs text-muted-foreground ml-2">
|
||||||
|
({items.length} {pluralRepairs(items.length)})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
@@ -176,13 +187,32 @@ function RepairRow({ item, onOpen }: { item: FeedItem; onOpen: () => void }) {
|
|||||||
<div className="text-xs text-muted-foreground truncate">
|
<div className="text-xs text-muted-foreground truncate">
|
||||||
Механик: {item.mechanic_name}
|
Механик: {item.mechanic_name}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-baseline justify-between gap-2">
|
||||||
<WorksLine works={item.works_preview.map((w) => w.name)} extra={item.works_extra} />
|
<WorksLine works={item.works_preview.map((w) => w.name)} extra={item.works_extra} />
|
||||||
|
<div className="text-sm font-semibold tabular-nums flex-shrink-0">
|
||||||
|
{fmtMoney(item.works_total || 0)} ₽
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function fmtMoney(n: number): string {
|
||||||
|
return new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 0 }).format(Math.round(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
function pluralRepairs(n: number): string {
|
||||||
|
const mod10 = n % 10;
|
||||||
|
const mod100 = n % 100;
|
||||||
|
if (mod100 >= 11 && mod100 <= 14) return "ремонтов";
|
||||||
|
if (mod10 === 1) return "ремонт";
|
||||||
|
if (mod10 >= 2 && mod10 <= 4) return "ремонта";
|
||||||
|
return "ремонтов";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function WorksLine({ works, extra }: { works: string[]; extra: number }) {
|
function WorksLine({ works, extra }: { works: string[]; extra: number }) {
|
||||||
// У40: ellipsis в одну строку. Соединяем имена работ через «·».
|
// У40: ellipsis в одну строку. Соединяем имена работ через «·».
|
||||||
if (works.length === 0) return null;
|
if (works.length === 0) return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user