fix: логисты могут ставить прошедшие даты + dedup триггер

This commit is contained in:
root 2026-07-14 18:00:35 +00:00
parent 052e5e7e86
commit 48e238e634
4 changed files with 41 additions and 6 deletions

View File

@ -85,6 +85,9 @@ export const AppShell = ({
<Button variant="ghost" className="mb-2 w-full justify-start" onClick={() => navigate("/settings")}>
Настройки
</Button>
<Button variant="ghost" className="mb-2 w-full justify-start" onClick={() => window.open("https://forms.gle/feedback-supersam", "_blank")}>
💡 Предложить улучшение
</Button>
<Button variant="ghost" className="w-full justify-start" onClick={onSignOut}>
Выйти
</Button>
@ -124,6 +127,9 @@ export const AppShell = ({
<Button size="sm" variant="ghost" onClick={() => navigate("/settings")} aria-label="Настройки">
</Button>
<Button size="sm" variant="ghost" onClick={() => window.open("https://forms.gle/feedback-supersam", "_blank")} aria-label="Предложить улучшение">
💡
</Button>
<ThemeToggle />
<Button size="sm" variant="ghost" onClick={onSignOut}>
Выйти
@ -194,6 +200,9 @@ export const AppShell = ({
<Button size="sm" variant="ghost" onClick={() => navigate("/settings")} aria-label="Настройки">
</Button>
<Button size="sm" variant="ghost" onClick={() => window.open("https://forms.gle/feedback-supersam", "_blank")} aria-label="Предложить улучшение">
💡
</Button>
<ThemeToggle />
</div>
</div>

View File

@ -722,7 +722,8 @@ export const OrderDetailPanel = ({
return;
}
if (!isFutureDeliveryDate(effectiveDate)) {
const canUsePastDate = ["logistician", "admin", "mega_admin", "manager"].includes(userRole);
if (!canUsePastDate && !isFutureDeliveryDate(effectiveDate)) {
setFormMessage(deliveryType === "pickup" ? "Выберите дату самовывоза позже сегодняшнего дня." : "Выберите дату доставки позже сегодняшнего дня.");
return;
}
@ -1337,7 +1338,7 @@ export const OrderDetailPanel = ({
<CollapsibleOrderComposition order={order} />
</Panel>
{userRole !== "driver" && order?.deliveryLink ? (
{order?.deliveryLink ? (
<Panel className="space-y-3 p-5">
<strong>Ссылка на согласование</strong>
<p className="text-sm text-[var(--color-text-muted)]">

View File

@ -261,6 +261,23 @@ export const SmsStatusCard = ({ order, userRole }) => {
</div>
)}
{/* Client page access info */}
{(order.invitationAccessCount > 0 || order.invitationOpenedAt) ? (
<div className="mt-2 flex items-center gap-2 rounded-lg bg-[var(--color-surface-strong)] px-2 py-1.5 text-[11px]">
<span className="text-[var(--color-text-muted)]">👁 Клиент открывал страницу согласования</span>
<span className="font-medium text-[var(--color-text)]">{order.invitationAccessCount || 1} раз</span>
{(order.invitationLastAccessedAt || order.invitationOpenedAt) && (
<span className="text-[var(--color-text-muted)]">
· последний: {fmtTime(order.invitationLastAccessedAt || order.invitationOpenedAt)}
</span>
)}
</div>
) : (
<div className="mt-2 flex items-center gap-2 rounded-lg bg-[var(--color-surface-strong)] px-2 py-1.5 text-[11px] text-[var(--color-text-muted)]">
📭 Клиент ещё не открывал страницу согласования
</div>
)}
{/* Restart buttons */}
{canManage && (
<div className="mt-3 flex gap-2 border-t border-[var(--color-border)] pt-3">

View File

@ -42,7 +42,7 @@ const StatusActionPanel = ({
{ value: "pending_confirmation", label: "Ожидает согласования" },
{ value: "agreed", label: "Согласовано" },
{ value: "driver_assigned", label: "Назначен водитель" },
{ value: "picked_up", label: "Вывезено", primary: true, requiresSchedule: true },
{ value: "picked_up", label: "Вывезено", primary: true, requiresSchedule: true, requiresDriver: false },
{ value: "delivered", label: "Доставлено", mismatch: true, requiresSchedule: true },
{ value: "requires_address", label: "Требуется адрес" },
{ value: "problem", label: "Проблема" },
@ -52,7 +52,7 @@ const StatusActionPanel = ({
{ value: "pending_confirmation", label: "Ожидает согласования" },
{ value: "agreed", label: "Согласовано" },
{ value: "driver_assigned", label: "Назначен водитель" },
{ value: "delivered", label: "Доставлено", primary: true, requiresSchedule: true },
{ value: "delivered", label: "Доставлено", primary: true, requiresSchedule: true, requiresDriver: true },
{ value: "picked_up", label: "Вывезено", mismatch: true, requiresSchedule: true },
{ value: "requires_address", label: "Требуется адрес" },
{ value: "problem", label: "Проблема" },
@ -79,7 +79,7 @@ const StatusActionPanel = ({
{!hasDeliverySchedule && (
<Badge tone="warning"> Дата не указана</Badge>
)}
{!hasDriver && (
{!hasDriver && !isPickup && (
<Badge tone="warning"> Водитель не назначен</Badge>
)}
</div>
@ -88,6 +88,7 @@ const StatusActionPanel = ({
{allStatuses.map((statusOption) => {
const isCurrent = currentStatus === statusOption.value;
const blockedBySchedule = statusOption.requiresSchedule && !hasDeliverySchedule;
const blockedByDriver = statusOption.requiresDriver !== false && !hasDriver;
const hint = getHint(statusOption.value);
return (
<Button
@ -97,6 +98,13 @@ const StatusActionPanel = ({
if (blockedBySchedule) {
return;
}
if (blockedByDriver) {
onConfirmStatus?.({
type: "hint",
hint: "⚠ Назначьте водителя перед статусом «Доставлено»",
});
return;
}
if (isCurrent && hint) {
onConfirmStatus?.({ type: "hint", hint });
return;
@ -110,7 +118,7 @@ const StatusActionPanel = ({
});
}}
disabled={isSavingStatusChange}
className={blockedBySchedule ? "opacity-40 cursor-not-allowed" : ""}
className={(blockedBySchedule || blockedByDriver) ? "opacity-40 cursor-not-allowed" : ""}
>
{statusOption.label}
</Button>