Merge pull request 'fix(delivery): simplify public choice flow' (#2) from codex/delivery-rpc-deploy into main
Reviewed-on: https://git.supersamsev.ru/mihail/supersam/pulls/2
This commit is contained in:
commit
488e478841
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
"По этому заказу согласование доставки завершено или передано логисту.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue