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",
|
||||
};
|
||||
|
||||
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}`); }}
|
||||
|
|
@ -293,4 +302,3 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
|||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}, "Ошибка назначения водителя");
|
||||
|
|
|
|||
Loading…
Reference in New Issue