Активация раздела «Ремонт» в Premium Мехник PWA: лента + создание ремонта, фоновая сверка прав (У35), различающие сообщения при отказе входа (У36). Backend для всех новых API в taxi-dashboard (отдельный commit eccaebe). Тулится на `ENABLE_STO_MODULE=true` в backend env. PR-4: Login + useMeSync. - src/api/me.ts: тип Me расширен (is_admin, can_login_pwa, departments, support — для У35/У36). - src/hooks/useMeSync.ts: фоновая сверка `/me` раз в 5 мин (У35). При 403 NO_PWA_ACCESS — сброс токена, revocation info в sessionStorage. IndexedDB-черновики не трогаются. - src/pages/Login.tsx: после успешного login делает getMe() и при 403 показывает баннер «У вас нет доступа» с кликабельным TG/телефонным контактом (У36). Баннер также рендерится при revocation после фоновой сверки. - src/App.tsx: useMeSync() подключён в корне. PR-5: лента ремонтов. - src/pages/ActionMenu.tsx: снят `comingSoon` с тайла «Ремонт». - src/pages/repairs/RepairsFeedPage.tsx: лента с infinite scroll (через IntersectionObserver), плейсхолдер «В парке пока нет ремонтов» (У41), ellipsis-имена работ + плюрализация «+N работ» (У40). - src/hooks/useRepairsFeed.ts: useInfiniteQuery + stale-while-revalidate + visibility refresh (У38). - src/lib/timeFormat.ts: формат «Сегодня / Вчера / 23 мая» (У39). - src/api/repairsFeed.ts: GET /api/v1/mechanic/repairs клиент. PR-7: создание ремонта (online-only). - src/api/repairsCreate.ts: searchCars, getCarContext, searchWorks, suggestParts, presignPhoto + uploadPhotoToTmp (PUT с правильным Content-Type), createRepair (POST с Idempotency-Key). - src/pages/repairs/CarSelectPage.tsx: поиск машины с debounce 200мс. - src/pages/repairs/CreateRepairPage.tsx: карточка создания ремонта с 5 inline-блоками (Шапка / Пробег / Фото / Работы / Запчасти / Замена масла). UUID v4 как Idempotency-Key. Карандашик-разблокировка цены с обязательным комментарием (У19). Мягкая подписка пробега масла на шапочный (У24). Дефолт next_in_km=10000 (У25). Полноэкранная модалка поиска работ. Routes: /repairs, /repairs/new, /repairs/new/:carId. Co-Authored-By: claude-flow <ruv@ruv.net>
99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
import { useNavigate } from "react-router-dom";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { getMe } from "@/api/me";
|
|
import { logout } from "@/api/auth";
|
|
import { Card } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
interface ActionTile {
|
|
key: string;
|
|
title: string;
|
|
description: string;
|
|
emoji: string;
|
|
onClick: () => void;
|
|
disabled?: boolean;
|
|
comingSoon?: boolean;
|
|
}
|
|
|
|
export default function ActionMenu() {
|
|
const navigate = useNavigate();
|
|
const meQuery = useQuery({ queryKey: ["me"], queryFn: getMe, staleTime: 60_000 });
|
|
|
|
const tiles: ActionTile[] = [
|
|
{
|
|
key: "inspect",
|
|
title: "Осмотр",
|
|
description: "Создать или продолжить осмотр машины",
|
|
emoji: "🔍",
|
|
onClick: () => navigate("/vehicles?action=inspect"),
|
|
},
|
|
{
|
|
key: "repair",
|
|
title: "Ремонт",
|
|
description: "Лента ремонтов парка + создать новый",
|
|
emoji: "🔧",
|
|
onClick: () => navigate("/repairs"),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
<header className="flex items-center justify-between p-4 border-b">
|
|
<div>
|
|
<h1 className="text-xl font-semibold">Premium Механик</h1>
|
|
{meQuery.data && (
|
|
<p className="text-xs text-muted-foreground">{meQuery.data.name}</p>
|
|
)}
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => {
|
|
logout();
|
|
navigate("/login", { replace: true });
|
|
}}
|
|
>
|
|
Выйти
|
|
</Button>
|
|
</header>
|
|
|
|
<main className="container max-w-2xl py-8 space-y-4">
|
|
<h2 className="text-lg font-semibold text-muted-foreground">
|
|
Что делаем?
|
|
</h2>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
{tiles.map((t) => (
|
|
<Card
|
|
key={t.key}
|
|
onClick={t.disabled ? undefined : t.onClick}
|
|
className={
|
|
"p-5 transition-shadow " +
|
|
(t.disabled
|
|
? "opacity-50 cursor-not-allowed"
|
|
: "cursor-pointer hover:shadow-md active:shadow-sm")
|
|
}
|
|
>
|
|
<div className="flex items-start gap-3">
|
|
<div className="text-3xl leading-none">{t.emoji}</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="font-semibold text-base">{t.title}</h3>
|
|
{t.comingSoon && (
|
|
<span className="text-[10px] uppercase tracking-wide bg-muted text-muted-foreground rounded px-1.5 py-0.5">
|
|
скоро
|
|
</span>
|
|
)}
|
|
</div>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
{t.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|