repairs: несколько замен масла за заезд (двигатель + коробка)
Блок «Замена масла» теперь мульти: чекбоксы Двигатель / Коробка (можно оба), общий одометр (одна замена — один пробег). Отправляем список oil_changes (1:N), стикер прикрепляем к первой выбранной. Тип 'transmission' объединяет АКПП/вариатор/робот — интервал по модели в отчёте СТО. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -72,7 +72,8 @@ export interface CreateRepairPayload {
|
|||||||
works: CreateWorkPayload[];
|
works: CreateWorkPayload[];
|
||||||
parts: CreatePartPayload[];
|
parts: CreatePartPayload[];
|
||||||
photos: CreatePhotoPayload[];
|
photos: CreatePhotoPayload[];
|
||||||
oil_change: CreateOilChangePayload | null;
|
// 1:N — за один заезд бывает несколько замен (двигатель + коробка).
|
||||||
|
oil_changes: CreateOilChangePayload[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateRepairResponse {
|
export interface CreateRepairResponse {
|
||||||
|
|||||||
@@ -61,14 +61,13 @@ interface PartRow {
|
|||||||
qty: number;
|
qty: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type OilKind = "engine" | "cvt" | "akpp";
|
|
||||||
|
|
||||||
interface OilChangeState {
|
interface OilChangeState {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
mileage_at_change: number | null;
|
mileage_at_change: number | null; // общий одометр — один на все замены заезда
|
||||||
mileage_is_dirty: boolean; // У24: мягкая подписка с шапочным
|
mileage_is_dirty: boolean; // У24: мягкая подписка с шапочным
|
||||||
next_in_km: number;
|
next_in_km: number; // интервал двигателя
|
||||||
oil_type: OilKind;
|
// Какие масла меняли за заезд (можно оба сразу):
|
||||||
|
types: { engine: boolean; transmission: boolean };
|
||||||
sticker: PhotoState | null;
|
sticker: PhotoState | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +100,7 @@ export default function CreateRepairPage() {
|
|||||||
mileage_at_change: null,
|
mileage_at_change: null,
|
||||||
mileage_is_dirty: false,
|
mileage_is_dirty: false,
|
||||||
next_in_km: 10000,
|
next_in_km: 10000,
|
||||||
oil_type: "engine",
|
types: { engine: true, transmission: false },
|
||||||
sticker: null,
|
sticker: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -272,6 +271,8 @@ export default function CreateRepairPage() {
|
|||||||
if (works.length === 0) return "Добавьте хотя бы одну работу";
|
if (works.length === 0) return "Добавьте хотя бы одну работу";
|
||||||
const unlockedNoComment = works.find((w) => w.unlocked && !w.override_comment.trim());
|
const unlockedNoComment = works.find((w) => w.unlocked && !w.override_comment.trim());
|
||||||
if (unlockedNoComment) return "Укажите комментарий к ручной правке цены";
|
if (unlockedNoComment) return "Укажите комментарий к ручной правке цены";
|
||||||
|
if (oilChange.enabled && !oilChange.types.engine && !oilChange.types.transmission)
|
||||||
|
return "Выберите тип масла (двигатель и/или коробка) или снимите «Была замена масла»";
|
||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -301,22 +302,25 @@ export default function CreateRepairPage() {
|
|||||||
})),
|
})),
|
||||||
parts: parts.map((p) => ({ name: p.name, qty: p.qty })),
|
parts: parts.map((p) => ({ name: p.name, qty: p.qty })),
|
||||||
photos: photoPayload,
|
photos: photoPayload,
|
||||||
oil_change: oilChange.enabled
|
oil_changes: oilChange.enabled
|
||||||
? {
|
? (["engine", "transmission"] as const)
|
||||||
|
.filter((t) => oilChange.types[t])
|
||||||
|
.map((t, idx) => ({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
mileage_at_change: oilChange.mileage_at_change,
|
mileage_at_change: oilChange.mileage_at_change,
|
||||||
next_in_km: oilChange.next_in_km,
|
next_in_km: t === "engine" ? oilChange.next_in_km : 0,
|
||||||
oil_type: oilChange.oil_type,
|
oil_type: t,
|
||||||
|
// Стикер прикрепляем к первой выбранной замене (обычно двигатель).
|
||||||
sticker_photo_uuid:
|
sticker_photo_uuid:
|
||||||
oilChange.sticker?.status === "uploaded"
|
idx === 0 && oilChange.sticker?.status === "uploaded"
|
||||||
? oilChange.sticker.presign.photo_uuid
|
? oilChange.sticker.presign.photo_uuid
|
||||||
: null,
|
: null,
|
||||||
sticker_extension:
|
sticker_extension:
|
||||||
oilChange.sticker?.status === "uploaded"
|
idx === 0 && oilChange.sticker?.status === "uploaded"
|
||||||
? oilChange.sticker.presign.extension
|
? oilChange.sticker.presign.extension
|
||||||
: null,
|
: null,
|
||||||
}
|
}))
|
||||||
: null,
|
: [],
|
||||||
},
|
},
|
||||||
idemKey.current,
|
idemKey.current,
|
||||||
);
|
);
|
||||||
@@ -585,25 +589,34 @@ export default function CreateRepairPage() {
|
|||||||
{oilChange.enabled && (
|
{oilChange.enabled && (
|
||||||
<div className="mt-3 space-y-3">
|
<div className="mt-3 space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-sm">Тип масла</Label>
|
<Label className="text-sm">Что меняли (можно оба)</Label>
|
||||||
<div className="mt-1 grid grid-cols-3 gap-2">
|
<div className="mt-1 grid grid-cols-2 gap-2">
|
||||||
{([["engine", "Двигатель"], ["cvt", "Вариатор"], ["akpp", "АКПП"]] as [OilKind, string][]).map(
|
{(
|
||||||
([v, l]) => (
|
[
|
||||||
|
["engine", "Двигатель"],
|
||||||
|
["transmission", "Коробка"],
|
||||||
|
] as ["engine" | "transmission", string][]
|
||||||
|
).map(([v, l]) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={v}
|
key={v}
|
||||||
onClick={() => setOilChange((oc) => ({ ...oc, oil_type: v }))}
|
onClick={() =>
|
||||||
|
setOilChange((oc) => ({ ...oc, types: { ...oc.types, [v]: !oc.types[v] } }))
|
||||||
|
}
|
||||||
className={`rounded-md border px-2 py-2 text-sm ${
|
className={`rounded-md border px-2 py-2 text-sm ${
|
||||||
oilChange.oil_type === v
|
oilChange.types[v]
|
||||||
? "border-primary bg-primary/10 font-medium text-primary"
|
? "border-primary bg-primary/10 font-medium text-primary"
|
||||||
: "border-input"
|
: "border-input"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
{oilChange.types[v] ? "✓ " : ""}
|
||||||
{l}
|
{l}
|
||||||
</button>
|
</button>
|
||||||
),
|
))}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
{!oilChange.types.engine && !oilChange.types.transmission && (
|
||||||
|
<p className="mt-1 text-xs text-destructive">Выберите хотя бы один тип масла.</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-sm">Пробег на момент замены</Label>
|
<Label className="text-sm">Пробег на момент замены</Label>
|
||||||
@@ -620,9 +633,9 @@ export default function CreateRepairPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{oilChange.oil_type === "engine" ? (
|
{oilChange.types.engine && (
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-sm">Следующая замена через (км)</Label>
|
<Label className="text-sm">Двигатель: следующая замена через (км)</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
@@ -632,10 +645,10 @@ export default function CreateRepairPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
)}
|
||||||
|
{oilChange.types.transmission && (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Интервал {oilChange.oil_type === "cvt" ? "вариатора" : "АКПП"} задаётся по модели авто в
|
Интервал коробки задаётся по модели авто в отчёте СТО.
|
||||||
отчёте СТО.
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user