fix(delivery): simplify public choice flow
This commit is contained in:
parent
fc74241378
commit
496abc761b
|
|
@ -52,7 +52,6 @@ export const DeliveryChoiceFlow = ({
|
||||||
invitation = {},
|
invitation = {},
|
||||||
selectedSlot = null,
|
selectedSlot = null,
|
||||||
onConfirmChoice = () => {},
|
onConfirmChoice = () => {},
|
||||||
onRequestNewLink = () => {},
|
|
||||||
}) => {
|
}) => {
|
||||||
const state = invitation.state || "awaiting_choice";
|
const state = invitation.state || "awaiting_choice";
|
||||||
const isActive = ACTIVE_STATES.has(state);
|
const isActive = ACTIVE_STATES.has(state);
|
||||||
|
|
@ -108,9 +107,6 @@ export const DeliveryChoiceFlow = ({
|
||||||
>
|
>
|
||||||
Сохранить
|
Сохранить
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" className="w-full sm:w-auto" onClick={onRequestNewLink}>
|
|
||||||
Запросить новую ссылку
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ describe("DeliveryChoiceFlow", () => {
|
||||||
customerName: "Мария Волкова",
|
customerName: "Мария Волкова",
|
||||||
}}
|
}}
|
||||||
onConfirmChoice={() => {}}
|
onConfirmChoice={() => {}}
|
||||||
onRequestNewLink={() => {}}
|
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(markup).toContain("Выберите время доставки");
|
expect(markup).toContain("Выберите время доставки");
|
||||||
expect(markup).toContain("Сохранить");
|
expect(markup).toContain("Сохранить");
|
||||||
expect(markup).toContain("Ожидает ответа клиента");
|
expect(markup).toContain("Ожидает ответа клиента");
|
||||||
|
expect(markup).not.toContain("Запросить новую ссылку");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders a disabled save action when nothing is selected", () => {
|
it("renders a disabled save action when nothing is selected", () => {
|
||||||
|
|
@ -31,7 +31,6 @@ describe("DeliveryChoiceFlow", () => {
|
||||||
customerName: "Мария Волкова",
|
customerName: "Мария Волкова",
|
||||||
}}
|
}}
|
||||||
onConfirmChoice={() => {}}
|
onConfirmChoice={() => {}}
|
||||||
onRequestNewLink={() => {}}
|
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -54,7 +53,6 @@ describe("DeliveryChoiceFlow", () => {
|
||||||
availableSlots: ["Первая половина дня", "Вторая половина дня"],
|
availableSlots: ["Первая половина дня", "Вторая половина дня"],
|
||||||
}}
|
}}
|
||||||
onConfirmChoice={() => {}}
|
onConfirmChoice={() => {}}
|
||||||
onRequestNewLink={() => {}}
|
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -75,7 +73,6 @@ describe("DeliveryChoiceFlow", () => {
|
||||||
customerName: "Мария Волкова",
|
customerName: "Мария Волкова",
|
||||||
}}
|
}}
|
||||||
onConfirmChoice={() => {}}
|
onConfirmChoice={() => {}}
|
||||||
onRequestNewLink={() => {}}
|
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -92,7 +89,6 @@ describe("DeliveryChoiceFlow", () => {
|
||||||
customerName: "Мария Волкова",
|
customerName: "Мария Волкова",
|
||||||
}}
|
}}
|
||||||
onConfirmChoice={() => {}}
|
onConfirmChoice={() => {}}
|
||||||
onRequestNewLink={() => {}}
|
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -110,7 +106,6 @@ describe("DeliveryChoiceFlow", () => {
|
||||||
availableSlots: ["15 апреля, первая половина дня"],
|
availableSlots: ["15 апреля, первая половина дня"],
|
||||||
}}
|
}}
|
||||||
onConfirmChoice={() => {}}
|
onConfirmChoice={() => {}}
|
||||||
onRequestNewLink={() => {}}
|
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,20 @@ import { formatDeliverySlotGroupLabel } from "./deliveryDateFormatting";
|
||||||
const groupSlotsByDate = (slots) => {
|
const groupSlotsByDate = (slots) => {
|
||||||
const groups = new Map();
|
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) {
|
for (const slot of slots) {
|
||||||
if (!groups.has(slot.date)) {
|
if (!groups.has(slot.date)) {
|
||||||
groups.set(slot.date, []);
|
groups.set(slot.date, []);
|
||||||
|
|
@ -14,7 +28,12 @@ const groupSlotsByDate = (slots) => {
|
||||||
groups.get(slot.date).push(slot);
|
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";
|
export { formatDeliverySlotGroupLabel } from "./deliveryDateFormatting";
|
||||||
|
|
@ -37,13 +56,6 @@ export const DeliverySlotsPicker = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<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]) => (
|
{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>
|
<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">
|
<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("послезавтра · 15.04.2026");
|
||||||
expect(markup).toContain("первая половина дня");
|
expect(markup).toContain("первая половина дня");
|
||||||
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", () => {
|
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 = () => {
|
export const ClientDeliveryPage = () => {
|
||||||
const { token } = useParams();
|
const { token } = useParams();
|
||||||
const [invitation, setInvitation] = React.useState(null);
|
const [invitation, setInvitation] = React.useState(null);
|
||||||
|
|
@ -216,6 +226,7 @@ export const ClientDeliveryPage = () => {
|
||||||
const savedChoiceLabel = effectiveSelectedSlot
|
const savedChoiceLabel = effectiveSelectedSlot
|
||||||
? `${formatDeliveryDate(effectiveSelectedSlot.date)} / ${effectiveSelectedSlot.time}`
|
? `${formatDeliveryDate(effectiveSelectedSlot.date)} / ${effectiveSelectedSlot.time}`
|
||||||
: "";
|
: "";
|
||||||
|
const heroDescription = getClientDeliveryHeroDescription(isActiveState, isChoiceSaved);
|
||||||
|
|
||||||
const handleSaveChoice = async () => {
|
const handleSaveChoice = async () => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
|
@ -263,10 +274,6 @@ export const ClientDeliveryPage = () => {
|
||||||
setError("");
|
setError("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRequestNewLink = () => {
|
|
||||||
setActionMessage("Если ссылка больше не работает, логист передаст новую ссылку вручную.");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen bg-[var(--color-bg)] px-3 py-4 sm:px-6 sm:py-8">
|
<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">
|
<Panel className="space-y-3 p-5 sm:p-6">
|
||||||
<p className="text-sm uppercase tracking-[0.24em] text-[var(--color-text-muted)]">Доставка заказа</p>
|
<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>
|
<h1 className="text-2xl font-semibold leading-tight sm:text-3xl">Согласование доставки</h1>
|
||||||
|
{heroDescription ? (
|
||||||
<p className="text-sm leading-6 text-[var(--color-text-muted)]">
|
<p className="text-sm leading-6 text-[var(--color-text-muted)]">
|
||||||
{isActiveState
|
{heroDescription}
|
||||||
? "Вам предложены варианты доставки. Выберите удобную дату и время."
|
|
||||||
: "По этому заказу согласование доставки завершено или передано логисту."}
|
|
||||||
</p>
|
</p>
|
||||||
|
) : null}
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
{isChoiceSaved && savedChoiceLabel ? (
|
{isChoiceSaved && savedChoiceLabel ? (
|
||||||
|
|
@ -334,7 +341,6 @@ export const ClientDeliveryPage = () => {
|
||||||
invitation={invitation}
|
invitation={invitation}
|
||||||
selectedSlot={effectiveSelectedSlot}
|
selectedSlot={effectiveSelectedSlot}
|
||||||
onConfirmChoice={handleSaveChoice}
|
onConfirmChoice={handleSaveChoice}
|
||||||
onRequestNewLink={handleRequestNewLink}
|
|
||||||
/>
|
/>
|
||||||
) : !isChoiceSaved ? (
|
) : !isChoiceSaved ? (
|
||||||
<DeliveryStateNotice state={invitationState} />
|
<DeliveryStateNotice state={invitationState} />
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { getInvitationReferenceLabel } from "../components/client/invitationRefe
|
||||||
import {
|
import {
|
||||||
buildDeliveryConfirmationPayload,
|
buildDeliveryConfirmationPayload,
|
||||||
buildSelectedSlotFromInvitation,
|
buildSelectedSlotFromInvitation,
|
||||||
|
getClientDeliveryHeroDescription,
|
||||||
groupSlotsFromInvitation,
|
groupSlotsFromInvitation,
|
||||||
} from "./ClientDeliveryPage";
|
} from "./ClientDeliveryPage";
|
||||||
|
|
||||||
|
|
@ -176,4 +177,14 @@ describe("ClientDeliveryPage helpers", () => {
|
||||||
}),
|
}),
|
||||||
).toBe("Счета: СФ Т\\ЕА-28687, СФ Т\\ЕА-28700");
|
).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