fix: action log — readable driver names, no duplicate columns, meaningful descriptions
This commit is contained in:
parent
805ceca152
commit
581a275bc0
|
|
@ -33,30 +33,11 @@ const ACTION_TONES = {
|
||||||
invitation_created: "accent",
|
invitation_created: "accent",
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatMSK = (isoStr) => {
|
|
||||||
if (!isoStr) return "—";
|
|
||||||
try {
|
|
||||||
const d = new Date(isoStr);
|
|
||||||
if (isNaN(d.getTime())) return isoStr;
|
|
||||||
const day = String(d.getDate()).padStart(2, "0");
|
|
||||||
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
||||||
const year = d.getFullYear();
|
|
||||||
const hours = String(d.getUTCHours() + 3).padStart(2, "0"); // UTC+3
|
|
||||||
const mins = String(d.getUTCMinutes()).padStart(2, "0");
|
|
||||||
const h = parseInt(hours, 10);
|
|
||||||
const adjustedHours = h >= 24 ? String(h - 24).padStart(2, "0") : hours;
|
|
||||||
return `${day}.${month}.${year} ${adjustedHours}:${mins}`;
|
|
||||||
} catch {
|
|
||||||
return isoStr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatMSKCorrect = (isoStr) => {
|
const formatMSKCorrect = (isoStr) => {
|
||||||
if (!isoStr) return "—";
|
if (!isoStr) return "—";
|
||||||
try {
|
try {
|
||||||
const d = new Date(isoStr);
|
const d = new Date(isoStr);
|
||||||
if (isNaN(d.getTime())) return isoStr;
|
if (isNaN(d.getTime())) return isoStr;
|
||||||
// Format in Moscow timezone
|
|
||||||
const msk = new Date(d.getTime() + 3 * 60 * 60 * 1000);
|
const msk = new Date(d.getTime() + 3 * 60 * 60 * 1000);
|
||||||
const day = String(msk.getUTCDate()).padStart(2, "0");
|
const day = String(msk.getUTCDate()).padStart(2, "0");
|
||||||
const month = String(msk.getUTCMonth() + 1).padStart(2, "0");
|
const month = String(msk.getUTCMonth() + 1).padStart(2, "0");
|
||||||
|
|
@ -77,6 +58,36 @@ const ROLE_LABELS = {
|
||||||
driver: "Водитель",
|
driver: "Водитель",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Human-readable description of what happened */
|
||||||
|
const getActionDescription = (log) => {
|
||||||
|
switch (log.action) {
|
||||||
|
case "status_change":
|
||||||
|
return `${log.old_value || "—"} → ${log.new_value || "—"}`;
|
||||||
|
case "driver_assigned":
|
||||||
|
return `Назначен: ${log.details?.driver_name || log.new_value || "водитель"}`;
|
||||||
|
case "driver_removed":
|
||||||
|
return `Снят: ${log.details?.driver_name || log.old_value || "водитель"}`;
|
||||||
|
case "date_assigned":
|
||||||
|
return `Дата: ${log.new_value || "—"}`;
|
||||||
|
case "client_confirmed":
|
||||||
|
return `Клиент подтвердил`;
|
||||||
|
case "client_cancelled":
|
||||||
|
return `Клиент отменил`;
|
||||||
|
case "cancelled":
|
||||||
|
return `Отменено`;
|
||||||
|
case "manual_confirmation":
|
||||||
|
return `Ручное подтверждение`;
|
||||||
|
case "paid_storage":
|
||||||
|
return `Платное хранение`;
|
||||||
|
case "sms_sent":
|
||||||
|
return `SMS отправлено`;
|
||||||
|
case "invitation_created":
|
||||||
|
return `Приглашение создано`;
|
||||||
|
default:
|
||||||
|
return log.new_value || getActionLabel(log.action);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const ActionLogPanel = ({ orderGroupId = null }) => {
|
export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -125,7 +136,9 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
(getActionLabel(log.action) || "").toLowerCase().includes(q) ||
|
(getActionLabel(log.action) || "").toLowerCase().includes(q) ||
|
||||||
(log.old_value || "").toLowerCase().includes(q) ||
|
(log.old_value || "").toLowerCase().includes(q) ||
|
||||||
(log.new_value || "").toLowerCase().includes(q) ||
|
(log.new_value || "").toLowerCase().includes(q) ||
|
||||||
(log.order_group_id || "").toLowerCase().includes(q)
|
(log.order_group_id || "").toLowerCase().includes(q) ||
|
||||||
|
(getActionDescription(log) || "").toLowerCase().includes(q) ||
|
||||||
|
(log.details?.driver_name || "").toLowerCase().includes(q)
|
||||||
);
|
);
|
||||||
}, [logs, filterSearch]);
|
}, [logs, filterSearch]);
|
||||||
|
|
||||||
|
|
@ -196,16 +209,14 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
<th className="pb-2 pr-3 font-medium">Дата/Время</th>
|
<th className="pb-2 pr-3 font-medium">Дата/Время</th>
|
||||||
<th className="pb-2 pr-3 font-medium">Сотрудник</th>
|
<th className="pb-2 pr-3 font-medium">Сотрудник</th>
|
||||||
<th className="pb-2 pr-3 font-medium">Действие</th>
|
<th className="pb-2 pr-3 font-medium">Действие</th>
|
||||||
<th className="pb-2 pr-3 font-medium">Действие</th>
|
<th className="pb-2 pr-3 font-medium">Описание</th>
|
||||||
<th className="pb-2 pr-3 font-medium">Было</th>
|
{!orderGroupId && <th className="pb-2 pr-3 font-medium">Группа</th>}
|
||||||
<th className="pb-2 pr-3 font-medium">Стало</th>
|
|
||||||
{!orderGroupId && <th className="pb-2 pr-3 font-medium">Группа доставки</th>}
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{filteredLogs.length === 0 && !loading && (
|
{filteredLogs.length === 0 && !loading && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={orderGroupId ? 6 : 7} className="py-6 text-center text-[var(--color-text-muted)]">
|
<td colSpan={orderGroupId ? 4 : 5} className="py-6 text-center text-[var(--color-text-muted)]">
|
||||||
Нет записей
|
Нет записей
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -227,16 +238,10 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
<Badge tone={ACTION_TONES[log.action] || "accent"}>
|
<Badge tone={ACTION_TONES[log.action] || "accent"}>
|
||||||
{getActionLabel(log.action)}
|
{getActionLabel(log.action)}
|
||||||
</Badge>
|
</Badge>
|
||||||
<div className="mt-0.5 text-xs text-[var(--color-text-muted)]">
|
|
||||||
{log.action === "status_change" && (log.new_value || "")}
|
|
||||||
{log.action === "driver_assigned" && "→ " + (log.new_value || "водитель")}
|
|
||||||
{log.action === "driver_removed" && "← " + (log.old_value || "водитель")}
|
|
||||||
{log.action === "date_assigned" && (log.new_value || "")}
|
|
||||||
{log.action === "paid_storage" && (log.new_value || "")}
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 pr-3 max-w-[150px] truncate">{log.old_value || "—"}</td>
|
<td className="py-2 pr-3">
|
||||||
<td className="py-2 pr-3 max-w-[150px] truncate">{log.new_value || "—"}</td>
|
<span className="text-sm">{getActionDescription(log)}</span>
|
||||||
|
</td>
|
||||||
{!orderGroupId && (
|
{!orderGroupId && (
|
||||||
<td className="py-2 pr-3 text-sm">
|
<td className="py-2 pr-3 text-sm">
|
||||||
{log.order_group_id ? (
|
{log.order_group_id ? (
|
||||||
|
|
@ -251,9 +256,9 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
{expandedId === log.id && (log.details || log.old_value?.length > 40 || log.new_value?.length > 40) && (
|
{expandedId === log.id && (
|
||||||
<tr className="bg-[var(--color-surface-strong)]">
|
<tr className="bg-[var(--color-surface-strong)]">
|
||||||
<td colSpan={orderGroupId ? 6 : 7} className="py-2 px-3">
|
<td colSpan={orderGroupId ? 4 : 5} className="py-2 px-3">
|
||||||
<div className="space-y-1 text-xs">
|
<div className="space-y-1 text-xs">
|
||||||
{log.old_value && (
|
{log.old_value && (
|
||||||
<div><span className="text-[var(--color-text-muted)]">Было:</span> {log.old_value}</div>
|
<div><span className="text-[var(--color-text-muted)]">Было:</span> {log.old_value}</div>
|
||||||
|
|
@ -263,16 +268,20 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
)}
|
)}
|
||||||
{log.details && typeof log.details === "object" && (
|
{log.details && typeof log.details === "object" && (
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
{Object.entries(log.details).map(([k, v]) => (
|
{Object.entries(log.details)
|
||||||
<div key={k}><span className="text-[var(--color-text-muted)]">{k}:</span> {String(v)}</div>
|
.filter(([k]) => k !== "source")
|
||||||
|
.map(([k, v]) => (
|
||||||
|
<div key={k}>
|
||||||
|
<span className="text-[var(--color-text-muted)]">
|
||||||
|
{{driver_name: "Водитель", driver_id: "ID водителя", problem_type: "Тип проблемы"}[k] || k}:
|
||||||
|
</span> {String(v)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{log.details && typeof log.details === "string" && (
|
|
||||||
<div><span className="text-[var(--color-text-muted)]">Детали:</span> {log.details}</div>
|
|
||||||
)}
|
|
||||||
{log.order_group_id && (
|
{log.order_group_id && (
|
||||||
<div><span className="text-[var(--color-text-muted)]">Группа:</span>{" "}
|
<div>
|
||||||
|
<span className="text-[var(--color-text-muted)]">Группа:</span>{" "}
|
||||||
<a href={`/dashboard/group/${log.order_group_id}`}
|
<a href={`/dashboard/group/${log.order_group_id}`}
|
||||||
className="text-[var(--color-accent)] hover:underline"
|
className="text-[var(--color-accent)] hover:underline"
|
||||||
onClick={(e) => { e.preventDefault(); navigate(`/dashboard/group/${log.order_group_id}`); }}
|
onClick={(e) => { e.preventDefault(); navigate(`/dashboard/group/${log.order_group_id}`); }}
|
||||||
|
|
@ -293,4 +302,3 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ export const assignDriverToOrderGroup = async ({
|
||||||
// Direct UPDATE — RLS allows manager/logistician/admin
|
// Direct UPDATE — RLS allows manager/logistician/admin
|
||||||
const { data: currentGroup, error: fetchCurrentError } = await client
|
const { data: currentGroup, error: fetchCurrentError } = await client
|
||||||
.from("order_groups")
|
.from("order_groups")
|
||||||
.select("delivery_status")
|
.select("delivery_status, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name)")
|
||||||
.eq("id", orderGroupId)
|
.eq("id", orderGroupId)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
|
|
@ -271,7 +271,12 @@ export const assignDriverToOrderGroup = async ({
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
await logAction({ orderGroupId, action: driverId ? "driver_assigned" : "driver_removed", newValue: driverId || "removed", details: { driver_id: driverId } }).catch(() => {});
|
const driverName = data?.assigned_driver?.name || driverId || "—";
|
||||||
|
const oldDriverName = currentGroup?.assigned_driver?.name || currentGroup?.assigned_driver_id || "";
|
||||||
|
const logPayload = driverId
|
||||||
|
? { orderGroupId, action: "driver_assigned", newValue: driverName, details: { driver_id: driverId, driver_name: driverName } }
|
||||||
|
: { orderGroupId, action: "driver_removed", oldValue: oldDriverName, newValue: "Снят", details: { driver_name: oldDriverName } };
|
||||||
|
await logAction(logPayload).catch(() => {});
|
||||||
|
|
||||||
return mapOrderGroupRowToDeliveryGroup(data);
|
return mapOrderGroupRowToDeliveryGroup(data);
|
||||||
}, "Ошибка назначения водителя");
|
}, "Ошибка назначения водителя");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue