diff --git a/src/pages/ClientDeliveryPage.jsx b/src/pages/ClientDeliveryPage.jsx
index 883f492..9140cf5 100644
--- a/src/pages/ClientDeliveryPage.jsx
+++ b/src/pages/ClientDeliveryPage.jsx
@@ -10,7 +10,47 @@ import {
fetchDeliveryInvitation,
} from "../services/deliveryInvitationApi";
-export const groupSlotsFromInvitation = (invitation) => {
+const DELIVERY_TIMEZONE = "Europe/Simferopol";
+
+const getBusinessTodayKey = (referenceDate = new Date()) => {
+ const parts = new Intl.DateTimeFormat("en-CA", {
+ timeZone: DELIVERY_TIMEZONE,
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ }).formatToParts(referenceDate);
+
+ const year = parts.find((part) => part.type === "year")?.value || "";
+ const month = parts.find((part) => part.type === "month")?.value || "";
+ const day = parts.find((part) => part.type === "day")?.value || "";
+
+ return `${year}-${month}-${day}`;
+};
+
+const addDaysToDateKey = (dateKey, amount) => {
+ const baseDate = new Date(`${dateKey}T12:00:00Z`);
+ if (Number.isNaN(baseDate.getTime())) {
+ return "";
+ }
+
+ baseDate.setUTCDate(baseDate.getUTCDate() + amount);
+ return baseDate.toISOString().slice(0, 10);
+};
+
+const getAllowedDeliveryDateKeys = (referenceDate = new Date()) => {
+ const todayKey = getBusinessTodayKey(referenceDate);
+ return new Set([addDaysToDateKey(todayKey, 1), addDaysToDateKey(todayKey, 2)].filter(Boolean));
+};
+
+const isAllowedDeliverySlotDate = (dateKey, referenceDate = new Date()) => {
+ if (!dateKey) {
+ return false;
+ }
+
+ return getAllowedDeliveryDateKeys(referenceDate).has(dateKey);
+};
+
+export const groupSlotsFromInvitation = (invitation, referenceDate = new Date()) => {
if (!invitation) {
return [];
}
@@ -44,6 +84,10 @@ export const groupSlotsFromInvitation = (invitation) => {
|| deliveryDate
|| "";
+ if (!isAllowedDeliverySlotDate(parsedDate, referenceDate)) {
+ return null;
+ }
+
return {
id: `slot-${index}-${raw}`,
date: parsedDate || deliveryDate || "",
@@ -64,6 +108,10 @@ export const groupSlotsFromInvitation = (invitation) => {
return null;
}
+ if (!isAllowedDeliverySlotDate(slotDate, referenceDate)) {
+ return null;
+ }
+
return {
id: slotId,
date: slotDate,
@@ -112,6 +160,7 @@ export const ClientDeliveryPage = () => {
const [selectedSlotId, setSelectedSlotId] = React.useState(null);
const [selectedSlot, setSelectedSlot] = React.useState(null);
const [choiceSaved, setChoiceSaved] = React.useState(false);
+ const referenceDate = React.useMemo(() => new Date(), [token]);
React.useEffect(() => {
let cancelled = false;
@@ -154,7 +203,7 @@ export const ClientDeliveryPage = () => {
};
}, [token]);
- const slots = groupSlotsFromInvitation(invitation);
+ const slots = groupSlotsFromInvitation(invitation, referenceDate);
const invitationState = invitation?.state || "awaiting_choice";
const isActiveState = ["awaiting_choice", "opened", "reminder_sent"].includes(invitationState);
@@ -189,7 +238,12 @@ export const ClientDeliveryPage = () => {
});
const loadedInvitation = await fetchDeliveryInvitation(token);
setInvitation(loadedInvitation);
- setSelectedSlot(buildSelectedSlotFromInvitation(loadedInvitation, groupSlotsFromInvitation(loadedInvitation)) || effectiveSelectedSlot);
+ setSelectedSlot(
+ buildSelectedSlotFromInvitation(
+ loadedInvitation,
+ groupSlotsFromInvitation(loadedInvitation, referenceDate),
+ ) || effectiveSelectedSlot,
+ );
setChoiceSaved(true);
setActionMessage("Выбор сохранен, спасибо.");
} catch (confirmError) {
@@ -286,7 +340,17 @@ export const ClientDeliveryPage = () => {
{actionMessage}
) : null}
- {!loading && error && invitation ? : null}
+ {!loading && error && invitation ? (
+
+
+ Не удалось сохранить
+
+
+ Проверьте выбор еще раз
+
+ {error}
+
+ ) : null}
);
diff --git a/src/pages/ClientDeliveryPage.test.js b/src/pages/ClientDeliveryPage.test.js
index bdd818e..401e517 100644
--- a/src/pages/ClientDeliveryPage.test.js
+++ b/src/pages/ClientDeliveryPage.test.js
@@ -13,7 +13,7 @@ describe("ClientDeliveryPage helpers", () => {
"2026-04-15, До обеда",
"2026-04-15, После обеда",
],
- }),
+ }, new Date("2026-04-14T09:00:00Z")),
).toEqual([
{
id: "slot-0-2026-04-15, До обеда",
@@ -58,7 +58,7 @@ describe("ClientDeliveryPage helpers", () => {
"2026-04-15, До обеда",
"2026-04-15, После обеда",
],
- }),
+ }, new Date("2026-04-13T09:00:00Z")),
).toEqual([
{
id: "slot-0-2026-04-14, До обеда",
@@ -114,7 +114,7 @@ describe("ClientDeliveryPage helpers", () => {
{ id: "slot-object", date: "2026-04-15", time: "Первая половина дня" },
42,
],
- }),
+ }, new Date("2026-04-14T09:00:00Z")),
).toEqual([
{
id: "slot-object",
@@ -123,4 +123,43 @@ describe("ClientDeliveryPage helpers", () => {
},
]);
});
+
+ it("keeps only tomorrow and the day after tomorrow", () => {
+ expect(
+ groupSlotsFromInvitation(
+ {
+ availableSlots: [
+ "2026-04-15, Первая половина дня",
+ "2026-04-15, Вторая половина дня",
+ "2026-04-16, Первая половина дня",
+ "2026-04-16, Вторая половина дня",
+ "2026-04-17, Первая половина дня",
+ "2026-04-17, Вторая половина дня",
+ ],
+ },
+ new Date("2026-04-14T09:00:00Z"),
+ ),
+ ).toEqual([
+ {
+ id: "slot-0-2026-04-15, Первая половина дня",
+ date: "2026-04-15",
+ time: "Первая половина дня",
+ },
+ {
+ id: "slot-1-2026-04-15, Вторая половина дня",
+ date: "2026-04-15",
+ time: "Вторая половина дня",
+ },
+ {
+ id: "slot-2-2026-04-16, Первая половина дня",
+ date: "2026-04-16",
+ time: "Первая половина дня",
+ },
+ {
+ id: "slot-3-2026-04-16, Вторая половина дня",
+ date: "2026-04-16",
+ time: "Вторая половина дня",
+ },
+ ]);
+ });
});
diff --git a/src/services/supabase/orderGroupRepository.js b/src/services/supabase/orderGroupRepository.js
index ee486d3..2a26538 100644
--- a/src/services/supabase/orderGroupRepository.js
+++ b/src/services/supabase/orderGroupRepository.js
@@ -15,6 +15,7 @@ const requireSupabase = () => {
};
const normalizeText = (value) => (value == null ? "" : String(value)).trim();
+const ALLOWED_DELIVERY_TIMES = new Set(["Первая половина дня", "Вторая половина дня"]);
const normalizePhone = (value) => normalizeText(value).replace(/[\s\-()]/g, "");
@@ -69,7 +70,13 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
);
const deliveryStatus = normalizeText(row.delivery_status) || "pending_confirmation";
const deliveryDate = normalizeText(row.delivery_date);
- const deliveryTime = normalizeText(row.delivery_time);
+ const rawDeliveryTime = normalizeText(row.delivery_time);
+ const rawDeliveryHalfDay = normalizeText(row.delivery_half_day);
+ const deliveryTime = ALLOWED_DELIVERY_TIMES.has(rawDeliveryTime)
+ ? rawDeliveryTime
+ : ALLOWED_DELIVERY_TIMES.has(rawDeliveryHalfDay)
+ ? rawDeliveryHalfDay
+ : "";
const deliveryAddress = normalizeText(row.delivery_address);
return {
@@ -114,8 +121,8 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
deliveryDate,
deliveryTime,
deliveryHalfDay: getOrderGroupDeliveryHalfDay({
- deliveryHalfDay: row.delivery_half_day,
- deliveryTime,
+ deliveryHalfDay: rawDeliveryHalfDay,
+ deliveryTime: rawDeliveryTime,
deliveryWindow: row.delivery_window,
sourceOrders: row.source_orders,
}),
@@ -126,8 +133,8 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
customerPhone,
customerDate,
deliveryAddress,
- row.delivery_half_day,
- deliveryTime,
+ rawDeliveryHalfDay,
+ rawDeliveryTime,
row.delivery_window,
deliveryStatus,
getOrderGroupDeliveryStatusLabel(deliveryStatus),
@@ -135,8 +142,8 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
row.status,
getOrderGroupStatusLabel(row.status),
getOrderGroupDeliveryHalfDay({
- deliveryHalfDay: row.delivery_half_day,
- deliveryTime,
+ deliveryHalfDay: rawDeliveryHalfDay,
+ deliveryTime: rawDeliveryTime,
deliveryWindow: row.delivery_window,
sourceOrders: row.source_orders,
}),
diff --git a/src/services/supabase/orderGroupRepository.test.js b/src/services/supabase/orderGroupRepository.test.js
index 274e095..2ded6f8 100644
--- a/src/services/supabase/orderGroupRepository.test.js
+++ b/src/services/supabase/orderGroupRepository.test.js
@@ -86,6 +86,26 @@ describe("mapOrderGroupRowToDeliveryGroup", () => {
expect(group.notReadyCount).toBe(0);
expect(group.deliveryDate).toBe("");
});
+
+ it("drops invalid delivery time values instead of showing them", () => {
+ const group = mapOrderGroupRowToDeliveryGroup({
+ id: "group-with-bad-time",
+ group_key: "9781632663|08.05.26",
+ customer_name: "Зиновьев Алексей Гаврилович",
+ customer_phone: "9781632663",
+ customer_date: "08.05.26",
+ delivery_time: "Зиновьев Алексей Гаврилович",
+ delivery_half_day: null,
+ order_numbers: ["СФ Т\\ЕА-26979"],
+ status: "ready_for_notification",
+ delivery_status: "pending_confirmation",
+ created_at: "2026-05-05 09:43:53.750061+00",
+ updated_at: "2026-05-05 09:43:53.750061+00",
+ });
+
+ expect(group.deliveryTime).toBe("");
+ expect(group.searchText).not.toContain("зиновьев алексей гаврилович зиновьев алексей гаврилович");
+ });
});
describe("updateOrderGroupDeliveryChoice", () => {