fix: логисты могут ставить прошедшие даты + dedup триггер
This commit is contained in:
parent
052e5e7e86
commit
48e238e634
|
|
@ -85,6 +85,9 @@ export const AppShell = ({
|
||||||
<Button variant="ghost" className="mb-2 w-full justify-start" onClick={() => navigate("/settings")}>
|
<Button variant="ghost" className="mb-2 w-full justify-start" onClick={() => navigate("/settings")}>
|
||||||
Настройки
|
Настройки
|
||||||
</Button>
|
</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 variant="ghost" className="w-full justify-start" onClick={onSignOut}>
|
||||||
Выйти
|
Выйти
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -124,6 +127,9 @@ export const AppShell = ({
|
||||||
<Button size="sm" variant="ghost" onClick={() => navigate("/settings")} aria-label="Настройки">
|
<Button size="sm" variant="ghost" onClick={() => navigate("/settings")} aria-label="Настройки">
|
||||||
⚙
|
⚙
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button size="sm" variant="ghost" onClick={() => window.open("https://forms.gle/feedback-supersam", "_blank")} aria-label="Предложить улучшение">
|
||||||
|
💡
|
||||||
|
</Button>
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<Button size="sm" variant="ghost" onClick={onSignOut}>
|
<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 size="sm" variant="ghost" onClick={() => navigate("/settings")} aria-label="Настройки">
|
||||||
⚙
|
⚙
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button size="sm" variant="ghost" onClick={() => window.open("https://forms.gle/feedback-supersam", "_blank")} aria-label="Предложить улучшение">
|
||||||
|
💡
|
||||||
|
</Button>
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -722,7 +722,8 @@ export const OrderDetailPanel = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isFutureDeliveryDate(effectiveDate)) {
|
const canUsePastDate = ["logistician", "admin", "mega_admin", "manager"].includes(userRole);
|
||||||
|
if (!canUsePastDate && !isFutureDeliveryDate(effectiveDate)) {
|
||||||
setFormMessage(deliveryType === "pickup" ? "Выберите дату самовывоза позже сегодняшнего дня." : "Выберите дату доставки позже сегодняшнего дня.");
|
setFormMessage(deliveryType === "pickup" ? "Выберите дату самовывоза позже сегодняшнего дня." : "Выберите дату доставки позже сегодняшнего дня.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1337,7 +1338,7 @@ export const OrderDetailPanel = ({
|
||||||
<CollapsibleOrderComposition order={order} />
|
<CollapsibleOrderComposition order={order} />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
{userRole !== "driver" && order?.deliveryLink ? (
|
{order?.deliveryLink ? (
|
||||||
<Panel className="space-y-3 p-5">
|
<Panel className="space-y-3 p-5">
|
||||||
<strong>Ссылка на согласование</strong>
|
<strong>Ссылка на согласование</strong>
|
||||||
<p className="text-sm text-[var(--color-text-muted)]">
|
<p className="text-sm text-[var(--color-text-muted)]">
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,23 @@ export const SmsStatusCard = ({ order, userRole }) => {
|
||||||
</div>
|
</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 */}
|
{/* Restart buttons */}
|
||||||
{canManage && (
|
{canManage && (
|
||||||
<div className="mt-3 flex gap-2 border-t border-[var(--color-border)] pt-3">
|
<div className="mt-3 flex gap-2 border-t border-[var(--color-border)] pt-3">
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ const StatusActionPanel = ({
|
||||||
{ value: "pending_confirmation", label: "Ожидает согласования" },
|
{ value: "pending_confirmation", label: "Ожидает согласования" },
|
||||||
{ value: "agreed", label: "Согласовано" },
|
{ value: "agreed", label: "Согласовано" },
|
||||||
{ value: "driver_assigned", 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: "delivered", label: "Доставлено", mismatch: true, requiresSchedule: true },
|
||||||
{ value: "requires_address", label: "Требуется адрес" },
|
{ value: "requires_address", label: "Требуется адрес" },
|
||||||
{ value: "problem", label: "Проблема" },
|
{ value: "problem", label: "Проблема" },
|
||||||
|
|
@ -52,7 +52,7 @@ const StatusActionPanel = ({
|
||||||
{ value: "pending_confirmation", label: "Ожидает согласования" },
|
{ value: "pending_confirmation", label: "Ожидает согласования" },
|
||||||
{ value: "agreed", label: "Согласовано" },
|
{ value: "agreed", label: "Согласовано" },
|
||||||
{ value: "driver_assigned", 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: "picked_up", label: "Вывезено", mismatch: true, requiresSchedule: true },
|
||||||
{ value: "requires_address", label: "Требуется адрес" },
|
{ value: "requires_address", label: "Требуется адрес" },
|
||||||
{ value: "problem", label: "Проблема" },
|
{ value: "problem", label: "Проблема" },
|
||||||
|
|
@ -79,7 +79,7 @@ const StatusActionPanel = ({
|
||||||
{!hasDeliverySchedule && (
|
{!hasDeliverySchedule && (
|
||||||
<Badge tone="warning">⚠ Дата не указана</Badge>
|
<Badge tone="warning">⚠ Дата не указана</Badge>
|
||||||
)}
|
)}
|
||||||
{!hasDriver && (
|
{!hasDriver && !isPickup && (
|
||||||
<Badge tone="warning">⚠ Водитель не назначен</Badge>
|
<Badge tone="warning">⚠ Водитель не назначен</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -88,6 +88,7 @@ const StatusActionPanel = ({
|
||||||
{allStatuses.map((statusOption) => {
|
{allStatuses.map((statusOption) => {
|
||||||
const isCurrent = currentStatus === statusOption.value;
|
const isCurrent = currentStatus === statusOption.value;
|
||||||
const blockedBySchedule = statusOption.requiresSchedule && !hasDeliverySchedule;
|
const blockedBySchedule = statusOption.requiresSchedule && !hasDeliverySchedule;
|
||||||
|
const blockedByDriver = statusOption.requiresDriver !== false && !hasDriver;
|
||||||
const hint = getHint(statusOption.value);
|
const hint = getHint(statusOption.value);
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -97,6 +98,13 @@ const StatusActionPanel = ({
|
||||||
if (blockedBySchedule) {
|
if (blockedBySchedule) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (blockedByDriver) {
|
||||||
|
onConfirmStatus?.({
|
||||||
|
type: "hint",
|
||||||
|
hint: "⚠ Назначьте водителя перед статусом «Доставлено»",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isCurrent && hint) {
|
if (isCurrent && hint) {
|
||||||
onConfirmStatus?.({ type: "hint", hint });
|
onConfirmStatus?.({ type: "hint", hint });
|
||||||
return;
|
return;
|
||||||
|
|
@ -110,7 +118,7 @@ const StatusActionPanel = ({
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={isSavingStatusChange}
|
disabled={isSavingStatusChange}
|
||||||
className={blockedBySchedule ? "opacity-40 cursor-not-allowed" : ""}
|
className={(blockedBySchedule || blockedByDriver) ? "opacity-40 cursor-not-allowed" : ""}
|
||||||
>
|
>
|
||||||
{statusOption.label}
|
{statusOption.label}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue