fix(delivery): simplify public choice flow

This commit is contained in:
Codex 2026-05-14 21:10:27 +03:00
parent fc74241378
commit 496abc761b
6 changed files with 65 additions and 28 deletions

View File

@ -52,7 +52,6 @@ export const DeliveryChoiceFlow = ({
invitation = {},
selectedSlot = null,
onConfirmChoice = () => {},
onRequestNewLink = () => {},
}) => {
const state = invitation.state || "awaiting_choice";
const isActive = ACTIVE_STATES.has(state);
@ -108,9 +107,6 @@ export const DeliveryChoiceFlow = ({
>
Сохранить
</Button>
<Button variant="secondary" className="w-full sm:w-auto" onClick={onRequestNewLink}>
Запросить новую ссылку
</Button>
</div>
</Panel>
);

View File

@ -13,13 +13,13 @@ describe("DeliveryChoiceFlow", () => {
customerName: "Мария Волкова",
}}
onConfirmChoice={() => {}}
onRequestNewLink={() => {}}
/>,
);
expect(markup).toContain("Выберите время доставки");
expect(markup).toContain("Сохранить");
expect(markup).toContain("Ожидает ответа клиента");
expect(markup).not.toContain("Запросить новую ссылку");
});
it("renders a disabled save action when nothing is selected", () => {
@ -31,7 +31,6 @@ describe("DeliveryChoiceFlow", () => {
customerName: "Мария Волкова",
}}
onConfirmChoice={() => {}}
onRequestNewLink={() => {}}
/>,
);
@ -54,7 +53,6 @@ describe("DeliveryChoiceFlow", () => {
availableSlots: ["Первая половина дня", "Вторая половина дня"],
}}
onConfirmChoice={() => {}}
onRequestNewLink={() => {}}
/>,
);
@ -75,7 +73,6 @@ describe("DeliveryChoiceFlow", () => {
customerName: "Мария Волкова",
}}
onConfirmChoice={() => {}}
onRequestNewLink={() => {}}
/>,
);
@ -92,7 +89,6 @@ describe("DeliveryChoiceFlow", () => {
customerName: "Мария Волкова",
}}
onConfirmChoice={() => {}}
onRequestNewLink={() => {}}
/>,
);
@ -110,7 +106,6 @@ describe("DeliveryChoiceFlow", () => {
availableSlots: ["15 апреля, первая половина дня"],
}}
onConfirmChoice={() => {}}
onRequestNewLink={() => {}}
/>,
);

View File

@ -6,6 +6,20 @@ import { formatDeliverySlotGroupLabel } from "./deliveryDateFormatting";
const groupSlotsByDate = (slots) => {
const groups = new Map();
const getSlotPriority = (slot) => {
const time = String(slot?.time || "").toLowerCase();
if (time.includes("первая") || time.includes("до обеда")) {
return 0;
}
if (time.includes("вторая") || time.includes("после обеда")) {
return 1;
}
return 2;
};
for (const slot of slots) {
if (!groups.has(slot.date)) {
groups.set(slot.date, []);
@ -14,7 +28,12 @@ const groupSlotsByDate = (slots) => {
groups.get(slot.date).push(slot);
}
return Array.from(groups.entries()).sort(([a], [b]) => a.localeCompare(b));
return Array.from(groups.entries())
.map(([date, dateSlots]) => [
date,
[...dateSlots].sort((left, right) => getSlotPriority(left) - getSlotPriority(right)),
])
.sort(([a], [b]) => a.localeCompare(b));
};
export { formatDeliverySlotGroupLabel } from "./deliveryDateFormatting";
@ -37,13 +56,6 @@ export const DeliverySlotsPicker = ({
return (
<div className="space-y-4">
<Panel className="p-5 sm:p-6">
<h3 className="text-lg font-semibold">Выберите день и половину дня доставки</h3>
<p className="mt-1 text-sm text-[var(--color-text-muted)]">
Раскройте нужный день, выберите подходящую половину и затем сохраните выбор ниже.
</p>
</Panel>
{grouped.map(([date, dateSlots]) => (
<details key={date} className="group rounded-[28px] border border-[var(--color-border)] bg-[var(--color-surface)] shadow-soft backdrop-blur" open>
<summary className="cursor-pointer list-none p-5 sm:p-6">

View File

@ -37,6 +37,23 @@ describe("DeliverySlotsPicker", () => {
expect(markup).toContain("послезавтра · 15.04.2026");
expect(markup).toContain("первая половина дня");
expect(markup).toContain("вторая половина дня");
expect(markup).not.toContain("выберите день и половину дня доставки");
});
it("renders the first half of day before the second half", () => {
const markup = renderToStaticMarkup(
<DeliverySlotsPicker
slots={[
{ date: "2026-04-14", time: "Вторая половина дня", id: "slot-2" },
{ date: "2026-04-14", time: "Первая половина дня", id: "slot-1" },
]}
onSelectSlot={() => {}}
selectedSlotId={null}
referenceDate={new Date("2026-04-13T09:00:00Z")}
/>,
).toLowerCase();
expect(markup.indexOf("первая половина дня")).toBeLessThan(markup.indexOf("вторая половина дня"));
});
it("marks the selected slot", () => {

View File

@ -152,6 +152,16 @@ export const buildSelectedSlotFromInvitation = (invitation, slots = []) => {
};
};
export const getClientDeliveryHeroDescription = (isActiveState, isChoiceSaved) => {
if (isChoiceSaved) {
return "";
}
return isActiveState
? "Вам предложены варианты доставки. Выберите удобную дату и время."
: "По этому заказу согласование доставки завершено или передано логисту.";
};
export const ClientDeliveryPage = () => {
const { token } = useParams();
const [invitation, setInvitation] = React.useState(null);
@ -216,6 +226,7 @@ export const ClientDeliveryPage = () => {
const savedChoiceLabel = effectiveSelectedSlot
? `${formatDeliveryDate(effectiveSelectedSlot.date)} / ${effectiveSelectedSlot.time}`
: "";
const heroDescription = getClientDeliveryHeroDescription(isActiveState, isChoiceSaved);
const handleSaveChoice = async () => {
if (!token) {
@ -263,10 +274,6 @@ export const ClientDeliveryPage = () => {
setError("");
};
const handleRequestNewLink = () => {
setActionMessage("Если ссылка больше не работает, логист передаст новую ссылку вручную.");
};
if (loading) {
return (
<main className="min-h-screen bg-[var(--color-bg)] px-3 py-4 sm:px-6 sm:py-8">
@ -301,11 +308,11 @@ export const ClientDeliveryPage = () => {
<Panel className="space-y-3 p-5 sm:p-6">
<p className="text-sm uppercase tracking-[0.24em] text-[var(--color-text-muted)]">Доставка заказа</p>
<h1 className="text-2xl font-semibold leading-tight sm:text-3xl">Согласование доставки</h1>
<p className="text-sm leading-6 text-[var(--color-text-muted)]">
{isActiveState
? "Вам предложены варианты доставки. Выберите удобную дату и время."
: "По этому заказу согласование доставки завершено или передано логисту."}
</p>
{heroDescription ? (
<p className="text-sm leading-6 text-[var(--color-text-muted)]">
{heroDescription}
</p>
) : null}
</Panel>
{isChoiceSaved && savedChoiceLabel ? (
@ -334,7 +341,6 @@ export const ClientDeliveryPage = () => {
invitation={invitation}
selectedSlot={effectiveSelectedSlot}
onConfirmChoice={handleSaveChoice}
onRequestNewLink={handleRequestNewLink}
/>
) : !isChoiceSaved ? (
<DeliveryStateNotice state={invitationState} />

View File

@ -3,6 +3,7 @@ import { getInvitationReferenceLabel } from "../components/client/invitationRefe
import {
buildDeliveryConfirmationPayload,
buildSelectedSlotFromInvitation,
getClientDeliveryHeroDescription,
groupSlotsFromInvitation,
} from "./ClientDeliveryPage";
@ -176,4 +177,14 @@ describe("ClientDeliveryPage helpers", () => {
}),
).toBe("Счета: СФ Т\\ЕА-28687, СФ Т\\ЕА-28700");
});
it("hides the hero helper text after the client saves the choice", () => {
expect(getClientDeliveryHeroDescription(true, true)).toBe("");
expect(getClientDeliveryHeroDescription(true, false)).toBe(
"Вам предложены варианты доставки. Выберите удобную дату и время.",
);
expect(getClientDeliveryHeroDescription(false, false)).toBe(
"По этому заказу согласование доставки завершено или передано логисту.",
);
});
});