refactor: Phase 3 resilience — ErrorBoundary, split saving states, extract useStopWords
- ErrorBoundary: Russian UI, compact mode for card-level errors, reload button - ErrorBoundary wraps OrderDetailPanel in GroupDetailPage + DeliverySetDetailPanel - Split isSavingDeliveryChoice into 3 independent states: isSavingDeliveryChoice (delivery tab save) isSavingDriverAssignment (driver assign) isSavingStatusChange (status change) - Extract useStopWords hook + matchesStopWord to shared hooks/useStopWords.js - Remove 3x duplicated matchesStopWord from OrderDetailPanel, DriverShipmentPanel, OrderCompositionPanel - Remove useStopWords + supabase import from OrderDetailPanel
This commit is contained in:
parent
129175fed7
commit
2f2dae686c
|
|
@ -12,7 +12,6 @@ class ErrorBoundary extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error, errorInfo) {
|
componentDidCatch(error, errorInfo) {
|
||||||
// Extract component stack for richer context
|
|
||||||
const componentInfo = {
|
const componentInfo = {
|
||||||
component: errorInfo?.componentStack || null,
|
component: errorInfo?.componentStack || null,
|
||||||
props: this.props,
|
props: this.props,
|
||||||
|
|
@ -25,47 +24,61 @@ class ErrorBoundary extends React.Component {
|
||||||
this.setState({ hasError: false, error: null });
|
this.setState({ hasError: false, error: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleReload = () => {
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
renderDefaultFallback() {
|
renderDefaultFallback() {
|
||||||
|
const { compact } = this.props;
|
||||||
|
|
||||||
|
if (compact) {
|
||||||
|
return (
|
||||||
|
<div className="rounded-[24px] border border-[var(--color-error)] bg-[var(--color-error-soft)] p-4 text-center">
|
||||||
|
<p className="text-sm text-[var(--color-text)]">Что-то пошло не так</p>
|
||||||
|
<button
|
||||||
|
onClick={this.handleRetry}
|
||||||
|
className="mt-2 rounded-xl bg-[var(--color-primary)] px-4 py-2 text-sm font-semibold text-white"
|
||||||
|
>
|
||||||
|
Попробовать снова
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex min-h-[460px] flex-col items-center justify-center p-8 text-center">
|
||||||
style={{
|
<div className="mb-4 text-5xl">⚠️</div>
|
||||||
display: 'flex',
|
<h2 className="mb-2 text-xl font-semibold text-[var(--color-text)]">
|
||||||
flexDirection: 'column',
|
Что-то пошло не так
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
padding: '2rem',
|
|
||||||
textAlign: 'center',
|
|
||||||
minHeight: '200px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h2 style={{ marginBottom: '0.5rem', color: '#e53e3e' }}>
|
|
||||||
Something went wrong
|
|
||||||
</h2>
|
</h2>
|
||||||
<p style={{ marginBottom: '1rem', color: '#718096', fontSize: '0.9rem' }}>
|
<p className="mb-6 max-w-md text-sm text-[var(--color-text-muted)]">
|
||||||
An unexpected error occurred. You can try again.
|
Произошла непредвиденная ошибка. Попробуйте обновить страницу или вернуться позже.
|
||||||
</p>
|
</p>
|
||||||
<button
|
{this.state.error && process.env.NODE_ENV === 'development' && (
|
||||||
onClick={this.handleRetry}
|
<pre className="mb-4 max-h-32 overflow-auto rounded-xl bg-[var(--color-surface-strong)] p-3 text-left text-xs text-[var(--color-error)]">
|
||||||
style={{
|
{this.state.error.message}
|
||||||
padding: '0.5rem 1.25rem',
|
</pre>
|
||||||
fontSize: '0.9rem',
|
)}
|
||||||
fontWeight: 600,
|
<div className="flex gap-3">
|
||||||
color: '#fff',
|
<button
|
||||||
backgroundColor: '#3182ce',
|
onClick={this.handleRetry}
|
||||||
border: 'none',
|
className="rounded-xl bg-[var(--color-primary)] px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:opacity-90"
|
||||||
borderRadius: '6px',
|
>
|
||||||
cursor: 'pointer',
|
Попробовать снова
|
||||||
}}
|
</button>
|
||||||
>
|
<button
|
||||||
Try Again
|
onClick={this.handleReload}
|
||||||
</button>
|
className="rounded-xl border border-[var(--color-border)] px-5 py-2.5 text-sm font-semibold text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface)]"
|
||||||
|
>
|
||||||
|
Обновить страницу
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
// Allow custom fallback render function
|
|
||||||
if (typeof this.props.fallback === 'function') {
|
if (typeof this.props.fallback === 'function') {
|
||||||
return this.props.fallback(this.state.error, this.handleRetry);
|
return this.props.fallback(this.state.error, this.handleRetry);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Badge } from "../UI/Badge";
|
||||||
import { Panel } from "../UI/Panel";
|
import { Panel } from "../UI/Panel";
|
||||||
import { getInvitationReferenceLabel } from "./invitationReference";
|
import { getInvitationReferenceLabel } from "./invitationReference";
|
||||||
import { supabase } from "../../supabaseClient";
|
import { supabase } from "../../supabaseClient";
|
||||||
|
import { matchesStopWord } from "../../hooks/useStopWords";
|
||||||
|
|
||||||
const flattenOrderProducts = (rawItems) => {
|
const flattenOrderProducts = (rawItems) => {
|
||||||
if (!Array.isArray(rawItems) || rawItems.length === 0) return [];
|
if (!Array.isArray(rawItems) || rawItems.length === 0) return [];
|
||||||
|
|
@ -64,12 +65,6 @@ const flattenOrderProducts = (rawItems) => {
|
||||||
return products;
|
return products;
|
||||||
};
|
};
|
||||||
|
|
||||||
const matchesStopWord = (name, stopWords) => {
|
|
||||||
if (!stopWords || !stopWords.length) return false;
|
|
||||||
const lower = name.toLowerCase();
|
|
||||||
return stopWords.some((sw) => lower.includes(sw.toLowerCase()));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OrderCompositionPanel = ({ invitation = {} }) => {
|
export const OrderCompositionPanel = ({ invitation = {} }) => {
|
||||||
const [stopWords, setStopWords] = React.useState([]);
|
const [stopWords, setStopWords] = React.useState([]);
|
||||||
const [stopWordsLoaded, setStopWordsLoaded] = React.useState(false);
|
const [stopWordsLoaded, setStopWordsLoaded] = React.useState(false);
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,7 @@ import { supabase } from "../../supabaseClient";
|
||||||
import { Badge } from "../UI/Badge";
|
import { Badge } from "../UI/Badge";
|
||||||
import { Button } from "../UI/Button";
|
import { Button } from "../UI/Button";
|
||||||
import { Panel } from "../UI/Panel";
|
import { Panel } from "../UI/Panel";
|
||||||
|
import { matchesStopWord } from "../../hooks/useStopWords";
|
||||||
const matchesStopWord = (name, stopWords) => {
|
|
||||||
if (!stopWords || !stopWords.length) return false;
|
|
||||||
const lower = name.toLowerCase();
|
|
||||||
return stopWords.some((sw) => lower.includes(sw.toLowerCase()));
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseOrderItems = (order) => {
|
const parseOrderItems = (order) => {
|
||||||
if (!order) return [];
|
if (!order) return [];
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Button } from "../UI/Button";
|
import { Button } from "../UI/Button";
|
||||||
import { OrderDetailPanel } from "../orders/OrderDetailPanel";
|
import { OrderDetailPanel } from "../orders/OrderDetailPanel";
|
||||||
|
import ErrorBoundary from "../ErrorBoundary";
|
||||||
|
|
||||||
export const DeliverySetDetailPanel = ({ deliverySet, onClose }) => {
|
export const DeliverySetDetailPanel = ({ deliverySet, onClose }) => {
|
||||||
if (!deliverySet) {
|
if (!deliverySet) {
|
||||||
|
|
@ -8,7 +9,9 @@ export const DeliverySetDetailPanel = ({ deliverySet, onClose }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<OrderDetailPanel order={deliverySet} />
|
<ErrorBoundary compact>
|
||||||
|
<OrderDetailPanel order={deliverySet} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
|
||||||
{onClose ? (
|
{onClose ? (
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const DriverAssignmentPanel = ({
|
||||||
order,
|
order,
|
||||||
userRole,
|
userRole,
|
||||||
canManageDelivery,
|
canManageDelivery,
|
||||||
isSavingDeliveryChoice,
|
isSavingDriverAssignment,
|
||||||
selectedDriverId,
|
selectedDriverId,
|
||||||
onDriverSelect,
|
onDriverSelect,
|
||||||
onConfirmDriver,
|
onConfirmDriver,
|
||||||
|
|
@ -63,7 +63,7 @@ const DriverAssignmentPanel = ({
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onDriverSelect(e.target.value);
|
onDriverSelect(e.target.value);
|
||||||
}}
|
}}
|
||||||
disabled={isSavingDeliveryChoice}
|
disabled={isSavingDriverAssignment}
|
||||||
>
|
>
|
||||||
<option value="">{order.assignedDriverId ? "Сменить водителя..." : "Выберите водителя..."}</option>
|
<option value="">{order.assignedDriverId ? "Сменить водителя..." : "Выберите водителя..."}</option>
|
||||||
{drivers.map((driver) => (
|
{drivers.map((driver) => (
|
||||||
|
|
@ -73,9 +73,9 @@ const DriverAssignmentPanel = ({
|
||||||
<Button
|
<Button
|
||||||
className="md:px-4 md:py-2 md:whitespace-nowrap md:self-start"
|
className="md:px-4 md:py-2 md:whitespace-nowrap md:self-start"
|
||||||
onClick={onConfirmDriver}
|
onClick={onConfirmDriver}
|
||||||
disabled={isSavingDeliveryChoice || !selectedDriverId}
|
disabled={isSavingDriverAssignment || !selectedDriverId}
|
||||||
>
|
>
|
||||||
{isSavingDeliveryChoice ? "Назначаем..." : "Назначить"}
|
{isSavingDriverAssignment ? "Назначаем..." : "Назначить"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ import { DriverShipmentPanel } from "../driver/DriverShipmentPanel";
|
||||||
import { CalendarWidget } from "./CalendarWidget";
|
import { CalendarWidget } from "./CalendarWidget";
|
||||||
import { StatusActionPanel } from "./StatusActionPanel";
|
import { StatusActionPanel } from "./StatusActionPanel";
|
||||||
import { DriverAssignmentPanel } from "./DriverAssignmentPanel";
|
import { DriverAssignmentPanel } from "./DriverAssignmentPanel";
|
||||||
import { supabase } from "../../supabaseClient";
|
import { matchesStopWord, useStopWords } from "../../hooks/useStopWords";
|
||||||
import {
|
import {
|
||||||
getOrderGroupDeliveryStatusLabel,
|
getOrderGroupDeliveryStatusLabel,
|
||||||
getOrderGroupDisplayStatusLabel,
|
getOrderGroupDisplayStatusLabel,
|
||||||
|
|
@ -326,27 +326,7 @@ const normalizeDateForInput = (value) => {
|
||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const matchesStopWord = (name, stopWords) => {
|
const renderValue = (value) => value || "Нет данных";
|
||||||
if (!stopWords || !stopWords.length) return false;
|
|
||||||
const lower = name.toLowerCase();
|
|
||||||
return stopWords.some((sw) => lower.includes(sw.toLowerCase()));
|
|
||||||
};
|
|
||||||
|
|
||||||
const useStopWords = () => {
|
|
||||||
const [stopWords, setStopWords] = React.useState([]);
|
|
||||||
const [active, setActive] = React.useState(true);
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!supabase) return;
|
|
||||||
Promise.all([
|
|
||||||
supabase.from("stop_words").select("word").then(r => r.data || []),
|
|
||||||
supabase.from("stop_words_scope").select("scope").eq("id", 1).single().then(r => r.data),
|
|
||||||
]).then(([words, scopeRow]) => {
|
|
||||||
setStopWords(words.map((d) => d.word));
|
|
||||||
setActive(scopeRow?.scope !== "client_only");
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
return { stopWords, active };
|
|
||||||
};
|
|
||||||
|
|
||||||
const CollapsibleOrderComposition = ({ order }) => {
|
const CollapsibleOrderComposition = ({ order }) => {
|
||||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||||
|
|
@ -431,7 +411,7 @@ const CollapsibleOrderComposition = ({ order }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PaidStoragePanel = ({ order, onChangeDeliveryStatus, isSavingDeliveryChoice, setFormMessage }) => {
|
const PaidStoragePanel = ({ order, onChangeDeliveryStatus, isSavingStatusChange, setFormMessage }) => {
|
||||||
const [showConfirm, setShowConfirm] = React.useState(false);
|
const [showConfirm, setShowConfirm] = React.useState(false);
|
||||||
const isPaidStorage = (order.deliveryStatus || order.delivery_status) === "paid_storage";
|
const isPaidStorage = (order.deliveryStatus || order.delivery_status) === "paid_storage";
|
||||||
|
|
||||||
|
|
@ -461,7 +441,7 @@ const PaidStoragePanel = ({ order, onChangeDeliveryStatus, isSavingDeliveryChoic
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={isSavingDeliveryChoice}
|
disabled={isSavingStatusChange}
|
||||||
>
|
>
|
||||||
Отменить платное хранение
|
Отменить платное хранение
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -497,14 +477,14 @@ const PaidStoragePanel = ({ order, onChangeDeliveryStatus, isSavingDeliveryChoic
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={isSavingDeliveryChoice}
|
disabled={isSavingStatusChange}
|
||||||
>
|
>
|
||||||
Да, перевести
|
Да, перевести
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => setShowConfirm(false)}
|
onClick={() => setShowConfirm(false)}
|
||||||
disabled={isSavingDeliveryChoice}
|
disabled={isSavingStatusChange}
|
||||||
>
|
>
|
||||||
Отмена
|
Отмена
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -514,7 +494,7 @@ const PaidStoragePanel = ({ order, onChangeDeliveryStatus, isSavingDeliveryChoic
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => setShowConfirm(true)}
|
onClick={() => setShowConfirm(true)}
|
||||||
disabled={isSavingDeliveryChoice}
|
disabled={isSavingStatusChange}
|
||||||
>
|
>
|
||||||
Перевести в платное хранение
|
Перевести в платное хранение
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -561,6 +541,8 @@ export const OrderDetailPanel = ({
|
||||||
canManageDelivery = false,
|
canManageDelivery = false,
|
||||||
onSaveManualDeliveryChoice,
|
onSaveManualDeliveryChoice,
|
||||||
isSavingDeliveryChoice = false,
|
isSavingDeliveryChoice = false,
|
||||||
|
isSavingDriverAssignment = false,
|
||||||
|
isSavingStatusChange = false,
|
||||||
drivers = [],
|
drivers = [],
|
||||||
onAssignDriver,
|
onAssignDriver,
|
||||||
onChangeDeliveryStatus,
|
onChangeDeliveryStatus,
|
||||||
|
|
@ -1004,7 +986,7 @@ export const OrderDetailPanel = ({
|
||||||
order={order}
|
order={order}
|
||||||
userRole={userRole}
|
userRole={userRole}
|
||||||
canManageDelivery={canManageDelivery}
|
canManageDelivery={canManageDelivery}
|
||||||
isSavingDeliveryChoice={isSavingDeliveryChoice}
|
isSavingDriverAssignment={isSavingDriverAssignment}
|
||||||
selectedDriverId={selectedDriverId}
|
selectedDriverId={selectedDriverId}
|
||||||
onDriverSelect={(id) => { setSelectedDriverId(id); setDriverMessage(""); }}
|
onDriverSelect={(id) => { setSelectedDriverId(id); setDriverMessage(""); }}
|
||||||
onConfirmDriver={() => setConfirmAction({ type: 'driver' })}
|
onConfirmDriver={() => setConfirmAction({ type: 'driver' })}
|
||||||
|
|
@ -1017,7 +999,7 @@ export const OrderDetailPanel = ({
|
||||||
order={order}
|
order={order}
|
||||||
userRole={userRole}
|
userRole={userRole}
|
||||||
canManageDelivery={canManageDelivery}
|
canManageDelivery={canManageDelivery}
|
||||||
isSavingDeliveryChoice={isSavingDeliveryChoice}
|
isSavingStatusChange={isSavingStatusChange}
|
||||||
onConfirmStatus={(action) => {
|
onConfirmStatus={(action) => {
|
||||||
if (action.type === "hint") {
|
if (action.type === "hint") {
|
||||||
setFormMessage(action.hint);
|
setFormMessage(action.hint);
|
||||||
|
|
@ -1035,7 +1017,7 @@ export const OrderDetailPanel = ({
|
||||||
<PaidStoragePanel
|
<PaidStoragePanel
|
||||||
order={order}
|
order={order}
|
||||||
onChangeDeliveryStatus={onChangeDeliveryStatus}
|
onChangeDeliveryStatus={onChangeDeliveryStatus}
|
||||||
isSavingDeliveryChoice={isSavingDeliveryChoice}
|
isSavingStatusChange={isSavingStatusChange}
|
||||||
setFormMessage={setFormMessage}
|
setFormMessage={setFormMessage}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -1112,7 +1094,7 @@ export const OrderDetailPanel = ({
|
||||||
<div className="flex items-center gap-3 mt-2">
|
<div className="flex items-center gap-3 mt-2">
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
disabled={isSavingDeliveryChoice}
|
disabled={isSavingStatusChange}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (pendingStatus.value === "delivered" && shipmentState && !shipmentState.canMarkDelivered) return;
|
if (pendingStatus.value === "delivered" && shipmentState && !shipmentState.canMarkDelivered) return;
|
||||||
onChangeDeliveryStatus({
|
onChangeDeliveryStatus({
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const StatusActionPanel = ({
|
||||||
order,
|
order,
|
||||||
userRole,
|
userRole,
|
||||||
canManageDelivery,
|
canManageDelivery,
|
||||||
isSavingDeliveryChoice,
|
isSavingStatusChange,
|
||||||
onConfirmStatus,
|
onConfirmStatus,
|
||||||
}) => {
|
}) => {
|
||||||
if (!canManageDelivery || !["manager", "logistician", "admin", "mega_admin"].includes(userRole) || !order) {
|
if (!canManageDelivery || !["manager", "logistician", "admin", "mega_admin"].includes(userRole) || !order) {
|
||||||
|
|
@ -53,7 +53,7 @@ const StatusActionPanel = ({
|
||||||
}
|
}
|
||||||
onConfirmStatus?.({ type: "status", status: statusOption.value });
|
onConfirmStatus?.({ type: "status", status: statusOption.value });
|
||||||
}}
|
}}
|
||||||
disabled={isSavingDeliveryChoice}
|
disabled={isSavingStatusChange}
|
||||||
>
|
>
|
||||||
{statusOption.label}
|
{statusOption.label}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ export const useOrderGroups = () => {
|
||||||
const [isLoading, setIsLoading] = React.useState(true);
|
const [isLoading, setIsLoading] = React.useState(true);
|
||||||
const [loadError, setLoadError] = React.useState("");
|
const [loadError, setLoadError] = React.useState("");
|
||||||
const [isSavingDeliveryChoice, setIsSavingDeliveryChoice] = React.useState(false);
|
const [isSavingDeliveryChoice, setIsSavingDeliveryChoice] = React.useState(false);
|
||||||
|
const [isSavingDriverAssignment, setIsSavingDriverAssignment] = React.useState(false);
|
||||||
|
const [isSavingStatusChange, setIsSavingStatusChange] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
@ -142,7 +144,7 @@ export const useOrderGroups = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const assignDriver = React.useCallback(async ({ orderGroupId, driverId }) => {
|
const assignDriver = React.useCallback(async ({ orderGroupId, driverId }) => {
|
||||||
setIsSavingDeliveryChoice(true);
|
setIsSavingDriverAssignment(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await assignDriverToOrderGroup({ orderGroupId, driverId });
|
const result = await assignDriverToOrderGroup({ orderGroupId, driverId });
|
||||||
|
|
@ -165,12 +167,12 @@ export const useOrderGroups = () => {
|
||||||
error: getErrorMessage(error, "Не удалось назначить водителя"),
|
error: getErrorMessage(error, "Не удалось назначить водителя"),
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
setIsSavingDeliveryChoice(false);
|
setIsSavingDriverAssignment(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const changeDeliveryStatus = React.useCallback(async ({ orderGroupId, status, details }) => {
|
const changeDeliveryStatus = React.useCallback(async ({ orderGroupId, status, details }) => {
|
||||||
setIsSavingDeliveryChoice(true);
|
setIsSavingStatusChange(true);
|
||||||
try {
|
try {
|
||||||
const result = await updateDeliveryStatus({ orderGroupId, status, details });
|
const result = await updateDeliveryStatus({ orderGroupId, status, details });
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
|
|
@ -189,7 +191,7 @@ export const useOrderGroups = () => {
|
||||||
error: getErrorMessage(error, "Не удалось обновить статус"),
|
error: getErrorMessage(error, "Не удалось обновить статус"),
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
setIsSavingDeliveryChoice(false);
|
setIsSavingStatusChange(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -210,6 +212,8 @@ export const useOrderGroups = () => {
|
||||||
assignDriver,
|
assignDriver,
|
||||||
changeDeliveryStatus,
|
changeDeliveryStatus,
|
||||||
isSavingDeliveryChoice,
|
isSavingDeliveryChoice,
|
||||||
|
isSavingDriverAssignment,
|
||||||
|
isSavingStatusChange,
|
||||||
isLoading,
|
isLoading,
|
||||||
loadError,
|
loadError,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React from "react";
|
||||||
|
import { supabase } from "../supabaseClient";
|
||||||
|
|
||||||
|
const matchesStopWord = (name, stopWords) => {
|
||||||
|
if (!stopWords || !stopWords.length) return false;
|
||||||
|
const lower = name.toLowerCase();
|
||||||
|
return stopWords.some((sw) => lower.includes(sw.toLowerCase()));
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStopWords = () => {
|
||||||
|
const [stopWords, setStopWords] = React.useState([]);
|
||||||
|
const [active, setActive] = React.useState(true);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!supabase) return;
|
||||||
|
Promise.all([
|
||||||
|
supabase.from("stop_words").select("word").then((r) => r.data || []),
|
||||||
|
supabase
|
||||||
|
.from("stop_words_scope")
|
||||||
|
.select("scope")
|
||||||
|
.eq("id", 1)
|
||||||
|
.single()
|
||||||
|
.then((r) => r.data),
|
||||||
|
]).then(([words, scopeRow]) => {
|
||||||
|
setStopWords(words.map((d) => d.word));
|
||||||
|
setActive(scopeRow?.scope !== "client_only");
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { stopWords, active };
|
||||||
|
};
|
||||||
|
|
||||||
|
export { matchesStopWord, useStopWords };
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Navigate, useNavigate, useParams, useLocation } from "react-router-dom";
|
import { Navigate, useNavigate, useParams, useLocation } from "react-router-dom";
|
||||||
import { OrderDetailPanel } from "../components/orders/OrderDetailPanel";
|
import { OrderDetailPanel } from "../components/orders/OrderDetailPanel";
|
||||||
|
import ErrorBoundary from "../components/ErrorBoundary";
|
||||||
import { Button } from "../components/UI/Button";
|
import { Button } from "../components/UI/Button";
|
||||||
import { Panel } from "../components/UI/Panel";
|
import { Panel } from "../components/UI/Panel";
|
||||||
import { SkeletonPanel } from "../components/UI/Loading";
|
import { SkeletonPanel } from "../components/UI/Loading";
|
||||||
|
|
@ -30,6 +31,8 @@ export const GroupDetailPage = () => {
|
||||||
setSelectedOrderGroupId,
|
setSelectedOrderGroupId,
|
||||||
saveManualDeliveryChoice,
|
saveManualDeliveryChoice,
|
||||||
isSavingDeliveryChoice,
|
isSavingDeliveryChoice,
|
||||||
|
isSavingDriverAssignment,
|
||||||
|
isSavingStatusChange,
|
||||||
assignDriver,
|
assignDriver,
|
||||||
changeDeliveryStatus,
|
changeDeliveryStatus,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
|
@ -97,16 +100,20 @@ export const GroupDetailPage = () => {
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<SkeletonPanel lines={6} />
|
<SkeletonPanel lines={6} />
|
||||||
) : order ? (
|
) : order ? (
|
||||||
<OrderDetailPanel
|
<ErrorBoundary compact>
|
||||||
|
<OrderDetailPanel
|
||||||
order={order}
|
order={order}
|
||||||
canManageDelivery={["manager", "logistician", "admin", "mega_admin"].includes(userRole)}
|
canManageDelivery={["manager", "logistician", "admin", "mega_admin"].includes(userRole)}
|
||||||
onSaveManualDeliveryChoice={saveManualDeliveryChoice}
|
onSaveManualDeliveryChoice={saveManualDeliveryChoice}
|
||||||
isSavingDeliveryChoice={isSavingDeliveryChoice}
|
isSavingDeliveryChoice={isSavingDeliveryChoice}
|
||||||
|
isSavingDriverAssignment={isSavingDriverAssignment}
|
||||||
|
isSavingStatusChange={isSavingStatusChange}
|
||||||
drivers={drivers}
|
drivers={drivers}
|
||||||
onAssignDriver={assignDriver}
|
onAssignDriver={assignDriver}
|
||||||
onChangeDeliveryStatus={changeDeliveryStatus}
|
onChangeDeliveryStatus={changeDeliveryStatus}
|
||||||
userRole={userRole}
|
userRole={userRole}
|
||||||
/>
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
) : (
|
) : (
|
||||||
<Panel className="p-6 text-sm text-[var(--color-text-muted)]">
|
<Panel className="p-6 text-sm text-[var(--color-text-muted)]">
|
||||||
Группа не найдена.
|
Группа не найдена.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue