fix: action log — readable driver names, no duplicate columns, meaningful descriptions

This commit is contained in:
root 2026-05-28 10:11:57 +00:00
parent 805ceca152
commit 581a275bc0
2 changed files with 59 additions and 46 deletions

View File

@ -33,30 +33,11 @@ const ACTION_TONES = {
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) => {
if (!isoStr) return "—";
try {
const d = new Date(isoStr);
if (isNaN(d.getTime())) return isoStr;
// Format in Moscow timezone
const msk = new Date(d.getTime() + 3 * 60 * 60 * 1000);
const day = String(msk.getUTCDate()).padStart(2, "0");
const month = String(msk.getUTCMonth() + 1).padStart(2, "0");
@ -77,6 +58,36 @@ const ROLE_LABELS = {
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 }) => {
const { user } = useAuth();
const navigate = useNavigate();
@ -125,7 +136,9 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
(getActionLabel(log.action) || "").toLowerCase().includes(q) ||
(log.old_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]);
@ -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>
{!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>
</thead>
<tbody>
{filteredLogs.length === 0 && !loading && (
<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>
</tr>
@ -227,16 +238,10 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
<Badge tone={ACTION_TONES[log.action] || "accent"}>
{getActionLabel(log.action)}
</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 className="py-2 pr-3 max-w-[150px] truncate">{log.old_value || "—"}</td>
<td className="py-2 pr-3 max-w-[150px] truncate">{log.new_value || "—"}</td>
<td className="py-2 pr-3">
<span className="text-sm">{getActionDescription(log)}</span>
</td>
{!orderGroupId && (
<td className="py-2 pr-3 text-sm">
{log.order_group_id ? (
@ -251,9 +256,9 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
</td>
)}
</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)]">
<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">
{log.old_value && (
<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" && (
<div className="space-y-0.5">
{Object.entries(log.details).map(([k, v]) => (
<div key={k}><span className="text-[var(--color-text-muted)]">{k}:</span> {String(v)}</div>
{Object.entries(log.details)
.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>
)}
{log.details && typeof log.details === "string" && (
<div><span className="text-[var(--color-text-muted)]">Детали:</span> {log.details}</div>
)}
{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}`}
className="text-[var(--color-accent)] hover:underline"
onClick={(e) => { e.preventDefault(); navigate(`/dashboard/group/${log.order_group_id}`); }}
@ -292,5 +301,4 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
{loading && <div className="py-4 text-center text-sm text-[var(--color-text-muted)]">Загрузка...</div>}
</Panel>
);
};
};

View File

@ -229,7 +229,7 @@ export const assignDriverToOrderGroup = async ({
// Direct UPDATE — RLS allows manager/logistician/admin
const { data: currentGroup, error: fetchCurrentError } = await client
.from("order_groups")
.select("delivery_status")
.select("delivery_status, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name)")
.eq("id", orderGroupId)
.single();
@ -271,7 +271,12 @@ export const assignDriverToOrderGroup = async ({
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);
}, "Ошибка назначения водителя");