fix(autocall,client): рабочие дни из графика админки — самовывоз пн-пт у робота и ссылки, доставка в ссылке читает график; тексты панели «Рабочие дни»
This commit is contained in:
parent
270ab39abc
commit
794c3ce5e3
|
|
@ -153,6 +153,8 @@ export const BusinessSchedulePanel = () => {
|
|||
<h1 className="mt-1 text-2xl font-bold leading-tight">Рабочие дни</h1>
|
||||
<p className="mt-1 text-sm text-[var(--color-text-muted)]">
|
||||
Настройте, в какие дни доступны доставка, самовывоз и отправка SMS-приглашений.
|
||||
Дни самовывоза — единый источник правды: их же использует страница клиента по ссылке
|
||||
и робот-автодозвон, в другие дни самовывоз не предлагается.
|
||||
</p>
|
||||
</div>
|
||||
{savedAt && !hasChanges && (
|
||||
|
|
@ -169,7 +171,8 @@ export const BusinessSchedulePanel = () => {
|
|||
Доставка
|
||||
</h2>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">
|
||||
Дни, в которые клиент может выбрать доставку. В остальные дни доставка не предлагается.
|
||||
Дни, в которые клиент может выбрать доставку по ссылке. В остальные дни доставка
|
||||
не предлагается.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -191,7 +194,8 @@ export const BusinessSchedulePanel = () => {
|
|||
Самовывоз
|
||||
</h2>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">
|
||||
Дни, в которые клиент может выбрать самовывоз. В остальные дни самовывоз не предлагается.
|
||||
Дни, в которые склад выдаёт самовывоз. В остальные дни самовывоз не предлагается —
|
||||
ни в ссылке клиента, ни в звонке робота.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { formatDeliveryDate } from "../components/client/deliveryDateFormatting"
|
|||
import {
|
||||
confirmDeliveryChoice,
|
||||
fetchDeliveryInvitation,
|
||||
fetchDeliveryWorkDays,
|
||||
fetchPickupWorkDays,
|
||||
} from "../services/deliveryInvitationApi";
|
||||
|
||||
|
|
@ -128,30 +129,52 @@ const isSundayKey = (dateKey) => {
|
|||
return d.getUTCDay() === 0;
|
||||
};
|
||||
|
||||
const getNextWorkdayKey = (dateKey) => {
|
||||
// Рабочие дни доставки из «Графика работы» (1=пн … 7=вс).
|
||||
// Дефолт — прежнее поведение страницы: все дни, кроме воскресенья.
|
||||
const DEFAULT_DELIVERY_DAYS = [1, 2, 3, 4, 5, 6];
|
||||
|
||||
const isAllowedDeliveryDayKey = (dateKey, workDays = DEFAULT_DELIVERY_DAYS) => {
|
||||
if (!dateKey) return false;
|
||||
const d = new Date(`${dateKey}T12:00:00Z`);
|
||||
if (Number.isNaN(d.getTime())) return false;
|
||||
const weekday = d.getUTCDay() === 0 ? 7 : d.getUTCDay();
|
||||
return workDays.includes(weekday);
|
||||
};
|
||||
|
||||
const getNextWorkdayKey = (dateKey, workDays = DEFAULT_DELIVERY_DAYS) => {
|
||||
let next = addDaysToDateKey(dateKey, 1);
|
||||
while (isSundayKey(next)) {
|
||||
let guard = 0;
|
||||
while (!isAllowedDeliveryDayKey(next, workDays) && guard < 14) {
|
||||
next = addDaysToDateKey(next, 1);
|
||||
guard += 1;
|
||||
}
|
||||
return next;
|
||||
};
|
||||
|
||||
const getAllowedDeliveryDateKeys = (referenceDate = new Date()) => {
|
||||
const getAllowedDeliveryDateKeys = (referenceDate = new Date(), workDays = DEFAULT_DELIVERY_DAYS) => {
|
||||
const todayKey = getBusinessTodayKey(referenceDate);
|
||||
const firstWorkday = getNextWorkdayKey(todayKey);
|
||||
const secondWorkday = getNextWorkdayKey(firstWorkday);
|
||||
const firstWorkday = getNextWorkdayKey(todayKey, workDays);
|
||||
const secondWorkday = getNextWorkdayKey(firstWorkday, workDays);
|
||||
return new Set([firstWorkday, secondWorkday].filter(Boolean));
|
||||
};
|
||||
|
||||
const isAllowedDeliverySlotDate = (dateKey, referenceDate = new Date()) => {
|
||||
const isAllowedDeliverySlotDate = (
|
||||
dateKey,
|
||||
referenceDate = new Date(),
|
||||
workDays = DEFAULT_DELIVERY_DAYS,
|
||||
) => {
|
||||
if (!dateKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getAllowedDeliveryDateKeys(referenceDate).has(dateKey);
|
||||
return getAllowedDeliveryDateKeys(referenceDate, workDays).has(dateKey);
|
||||
};
|
||||
|
||||
export const groupSlotsFromInvitation = (invitation, referenceDate = new Date()) => {
|
||||
export const groupSlotsFromInvitation = (
|
||||
invitation,
|
||||
referenceDate = new Date(),
|
||||
workDays = DEFAULT_DELIVERY_DAYS,
|
||||
) => {
|
||||
if (!invitation) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -161,10 +184,10 @@ export const groupSlotsFromInvitation = (invitation, referenceDate = new Date())
|
|||
const deliveryTime = invitation.deliveryTime;
|
||||
|
||||
if (!rawSlots.length && !deliveryDate) {
|
||||
// Fallback: generate default delivery slots (next 2 workdays, both halves, skip Sunday)
|
||||
// Fallback: generate default delivery slots (next 2 workdays, both halves)
|
||||
const todayKey = getBusinessTodayKey(referenceDate);
|
||||
const firstWorkday = getNextWorkdayKey(todayKey);
|
||||
const secondWorkday = getNextWorkdayKey(firstWorkday);
|
||||
const firstWorkday = getNextWorkdayKey(todayKey, workDays);
|
||||
const secondWorkday = getNextWorkdayKey(firstWorkday, workDays);
|
||||
return [
|
||||
{ id: `slot-${firstWorkday}-first`, date: firstWorkday, time: "Первая половина дня" },
|
||||
{ id: `slot-${firstWorkday}-second`, date: firstWorkday, time: "Вторая половина дня" },
|
||||
|
|
@ -194,7 +217,7 @@ export const groupSlotsFromInvitation = (invitation, referenceDate = new Date())
|
|||
|| deliveryDate
|
||||
|| "";
|
||||
|
||||
if (!isAllowedDeliverySlotDate(parsedDate, referenceDate)) {
|
||||
if (!isAllowedDeliverySlotDate(parsedDate, referenceDate, workDays)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +241,7 @@ export const groupSlotsFromInvitation = (invitation, referenceDate = new Date())
|
|||
return null;
|
||||
}
|
||||
|
||||
if (!isAllowedDeliverySlotDate(slotDate, referenceDate)) {
|
||||
if (!isAllowedDeliverySlotDate(slotDate, referenceDate, workDays)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -306,10 +329,16 @@ export const ClientDeliveryPage = () => {
|
|||
[token, invitation?.smsSentAt],
|
||||
);
|
||||
|
||||
// Рабочие дни самовывоза — из графика компании, те же, что называет робот.
|
||||
// Рабочие дни — из графика компании, те же, что называет робот.
|
||||
const [pickupWorkDays, setPickupWorkDays] = React.useState([1, 2, 3, 4, 5]);
|
||||
const [deliveryWorkDays, setDeliveryWorkDays] = React.useState(DEFAULT_DELIVERY_DAYS);
|
||||
React.useEffect(() => {
|
||||
let cancelled = false;
|
||||
fetchDeliveryWorkDays()
|
||||
.then((days) => {
|
||||
if (!cancelled) setDeliveryWorkDays(days);
|
||||
})
|
||||
.catch(() => {});
|
||||
fetchPickupWorkDays()
|
||||
.then((days) => {
|
||||
if (!cancelled) setPickupWorkDays(days);
|
||||
|
|
@ -365,7 +394,7 @@ export const ClientDeliveryPage = () => {
|
|||
};
|
||||
}, [token]);
|
||||
|
||||
const slots = groupSlotsFromInvitation(invitation, referenceDate);
|
||||
const slots = groupSlotsFromInvitation(invitation, referenceDate, deliveryWorkDays);
|
||||
|
||||
const invitationState = invitation?.state || "awaiting_choice";
|
||||
const isActiveState = ["awaiting_choice", "opened", "reminder_sent"].includes(invitationState);
|
||||
|
|
@ -412,7 +441,7 @@ export const ClientDeliveryPage = () => {
|
|||
setSelectedSlot(
|
||||
buildSelectedSlotFromInvitation(
|
||||
loadedInvitation,
|
||||
groupSlotsFromInvitation(loadedInvitation, referenceDate),
|
||||
groupSlotsFromInvitation(loadedInvitation, referenceDate, deliveryWorkDays),
|
||||
) || effectiveSelectedSlot,
|
||||
);
|
||||
setChoiceSaved(true);
|
||||
|
|
|
|||
|
|
@ -184,30 +184,46 @@ export const __resetLocalDeliveryInvitationCache = () => {
|
|||
};
|
||||
|
||||
/**
|
||||
* Рабочие дни самовывоза из графика компании («График работы»).
|
||||
* Робот и страница клиента обязаны предлагать одни и те же дни, иначе
|
||||
* клиент выберет день, который склад не работает (например, воскресенье).
|
||||
* Номера дней: 1=пн … 7=вс.
|
||||
* Рабочие дни из графика компании («График работы» в админке).
|
||||
* Админка — единственный источник правды: и страница клиента, и робот-автодозвон
|
||||
* обязаны предлагать одни и те же дни, иначе клиент выберет день, когда склад
|
||||
* или доставка не работают. Номера дней: 1=пн … 7=вс.
|
||||
*/
|
||||
export const fetchPickupWorkDays = async () => {
|
||||
const fallback = [1, 2, 3, 4, 5];
|
||||
const SCHEDULE_FALLBACK = {
|
||||
deliveryDays: [1, 2, 3, 4, 5, 6],
|
||||
pickupDays: [1, 2, 3, 4, 5],
|
||||
smsDays: [1, 2, 3, 4, 5],
|
||||
};
|
||||
|
||||
const normalizeDays = (days, fallback) => {
|
||||
const parsed = (Array.isArray(days) ? days : [])
|
||||
.map((d) => Number(d))
|
||||
.filter((d) => Number.isInteger(d) && d >= 1 && d <= 7);
|
||||
return parsed.length ? parsed : fallback;
|
||||
};
|
||||
|
||||
export const fetchBusinessSchedule = async () => {
|
||||
if (!hasSupabaseConfig || !supabase?.rpc) {
|
||||
return fallback;
|
||||
return { ...SCHEDULE_FALLBACK };
|
||||
}
|
||||
try {
|
||||
const { data, error } = await supabase.rpc("get_business_schedule");
|
||||
if (error) throw error;
|
||||
const days = Array.isArray(data?.pickupDays) ? data.pickupDays : [];
|
||||
const parsed = days
|
||||
.map((d) => Number(d))
|
||||
.filter((d) => Number.isInteger(d) && d >= 1 && d <= 7);
|
||||
return parsed.length ? parsed : fallback;
|
||||
return {
|
||||
deliveryDays: normalizeDays(data?.deliveryDays, SCHEDULE_FALLBACK.deliveryDays),
|
||||
pickupDays: normalizeDays(data?.pickupDays, SCHEDULE_FALLBACK.pickupDays),
|
||||
smsDays: normalizeDays(data?.smsDays, SCHEDULE_FALLBACK.smsDays),
|
||||
};
|
||||
} catch (e) {
|
||||
console.warn("Не удалось получить график самовывоза, беру пн-пт", e);
|
||||
return fallback;
|
||||
console.warn("Не удалось получить график работы, беру значения по умолчанию", e);
|
||||
return { ...SCHEDULE_FALLBACK };
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchPickupWorkDays = async () => (await fetchBusinessSchedule()).pickupDays;
|
||||
|
||||
export const fetchDeliveryWorkDays = async () => (await fetchBusinessSchedule()).deliveryDays;
|
||||
|
||||
export const fetchDeliveryInvitation = async (token) => {
|
||||
if (!token) {
|
||||
throw new Error("Token is required");
|
||||
|
|
|
|||
Loading…
Reference in New Issue