fix: action log — resolve UUID to names, show old→new, add oldValue for driver reassignment
This commit is contained in:
parent
581a275bc0
commit
1e0344ee34
|
|
@ -4,6 +4,8 @@ import { Badge } from "../UI/Badge";
|
||||||
import { fetchActionLogs, getActionLabel } from "../../services/supabase/actionLogService";
|
import { fetchActionLogs, getActionLabel } from "../../services/supabase/actionLogService";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useAuth } from "../../context/AuthContext";
|
import { useAuth } from "../../context/AuthContext";
|
||||||
|
import { safeSupabaseCall } from "../../services/safeSupabaseCall";
|
||||||
|
import { hasSupabaseConfig, supabase } from "../../supabaseClient";
|
||||||
|
|
||||||
const ACTIONS = [
|
const ACTIONS = [
|
||||||
"status_change",
|
"status_change",
|
||||||
|
|
@ -33,6 +35,16 @@ const ACTION_TONES = {
|
||||||
invitation_created: "accent",
|
invitation_created: "accent",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ROLE_LABELS = {
|
||||||
|
mega_admin: "Мега-админ",
|
||||||
|
admin: "Админ",
|
||||||
|
manager: "Менеджер",
|
||||||
|
logistician: "Логист",
|
||||||
|
driver: "Водитель",
|
||||||
|
};
|
||||||
|
|
||||||
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||||
|
|
||||||
const formatMSKCorrect = (isoStr) => {
|
const formatMSKCorrect = (isoStr) => {
|
||||||
if (!isoStr) return "—";
|
if (!isoStr) return "—";
|
||||||
try {
|
try {
|
||||||
|
|
@ -50,44 +62,6 @@ const formatMSKCorrect = (isoStr) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ROLE_LABELS = {
|
|
||||||
mega_admin: "Мега-админ",
|
|
||||||
admin: "Админ",
|
|
||||||
manager: "Менеджер",
|
|
||||||
logistician: "Логист",
|
|
||||||
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();
|
||||||
|
|
@ -99,6 +73,38 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
const [filterDateTo, setFilterDateTo] = useState("");
|
const [filterDateTo, setFilterDateTo] = useState("");
|
||||||
const [filterSearch, setFilterSearch] = useState("");
|
const [filterSearch, setFilterSearch] = useState("");
|
||||||
const [expandedId, setExpandedId] = useState(null);
|
const [expandedId, setExpandedId] = useState(null);
|
||||||
|
const [userNames, setUserNames] = useState({});
|
||||||
|
|
||||||
|
// Fetch user name map for resolving UUIDs
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchNames = async () => {
|
||||||
|
if (!hasSupabaseConfig || !supabase) return;
|
||||||
|
const result = await safeSupabaseCall(
|
||||||
|
async () => {
|
||||||
|
const { data, error } = await supabase.from("users").select("id, name");
|
||||||
|
if (error) throw error;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
"Ошибка загрузки пользователей"
|
||||||
|
);
|
||||||
|
if (result && !result.error) {
|
||||||
|
const map = {};
|
||||||
|
(Array.isArray(result) ? result : result.data || []).forEach((u) => {
|
||||||
|
map[u.id] = u.name;
|
||||||
|
});
|
||||||
|
setUserNames(map);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchNames();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resolveName = useCallback(
|
||||||
|
(uuid) => {
|
||||||
|
if (!uuid || !UUID_RE.test(uuid)) return uuid;
|
||||||
|
return userNames[uuid] || uuid;
|
||||||
|
},
|
||||||
|
[userNames]
|
||||||
|
);
|
||||||
|
|
||||||
const loadLogs = useCallback(async () => {
|
const loadLogs = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -142,6 +148,45 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
);
|
);
|
||||||
}, [logs, filterSearch]);
|
}, [logs, filterSearch]);
|
||||||
|
|
||||||
|
/** Human-readable description */
|
||||||
|
const getActionDescription = (log) => {
|
||||||
|
switch (log.action) {
|
||||||
|
case "status_change": {
|
||||||
|
const oldVal = resolveName(log.old_value) || "—";
|
||||||
|
const newVal = resolveName(log.new_value) || "—";
|
||||||
|
return `${oldVal} → ${newVal}`;
|
||||||
|
}
|
||||||
|
case "driver_assigned": {
|
||||||
|
const name = log.details?.driver_name || resolveName(log.new_value) || "водитель";
|
||||||
|
return `Назначен: ${name}`;
|
||||||
|
}
|
||||||
|
case "driver_removed": {
|
||||||
|
const name = log.details?.driver_name || resolveName(log.old_value) || "водитель";
|
||||||
|
return `Снят: ${name}`;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActionDesc = getActionDescription;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel className="space-y-4 p-5">
|
<Panel className="space-y-4 p-5">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
|
|
@ -240,7 +285,7 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
</Badge>
|
</Badge>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 pr-3">
|
<td className="py-2 pr-3">
|
||||||
<span className="text-sm">{getActionDescription(log)}</span>
|
<span className="text-sm">{getActionDesc(log)}</span>
|
||||||
</td>
|
</td>
|
||||||
{!orderGroupId && (
|
{!orderGroupId && (
|
||||||
<td className="py-2 pr-3 text-sm">
|
<td className="py-2 pr-3 text-sm">
|
||||||
|
|
@ -261,10 +306,10 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
<td colSpan={orderGroupId ? 4 : 5} 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> {resolveName(log.old_value)}</div>
|
||||||
)}
|
)}
|
||||||
{log.new_value && (
|
{log.new_value && (
|
||||||
<div><span className="text-[var(--color-text-muted)]">Стало:</span> {log.new_value}</div>
|
<div><span className="text-[var(--color-text-muted)]">Стало:</span> {resolveName(log.new_value)}</div>
|
||||||
)}
|
)}
|
||||||
{log.details && typeof log.details === "object" && (
|
{log.details && typeof log.details === "object" && (
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
|
|
@ -273,8 +318,8 @@ export const ActionLogPanel = ({ orderGroupId = null }) => {
|
||||||
.map(([k, v]) => (
|
.map(([k, v]) => (
|
||||||
<div key={k}>
|
<div key={k}>
|
||||||
<span className="text-[var(--color-text-muted)]">
|
<span className="text-[var(--color-text-muted)]">
|
||||||
{{driver_name: "Водитель", driver_id: "ID водителя", problem_type: "Тип проблемы"}[k] || k}:
|
{{driver_name: "Водитель", driver_id: "ID", problem_type: "Тип проблемы", delivery_date_source: "Источник даты"}[k] || k}:
|
||||||
</span> {String(v)}
|
</span> {UUID_RE.test(String(v)) ? resolveName(String(v)) : String(v)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -274,7 +274,7 @@ export const assignDriverToOrderGroup = async ({
|
||||||
const driverName = data?.assigned_driver?.name || driverId || "—";
|
const driverName = data?.assigned_driver?.name || driverId || "—";
|
||||||
const oldDriverName = currentGroup?.assigned_driver?.name || currentGroup?.assigned_driver_id || "";
|
const oldDriverName = currentGroup?.assigned_driver?.name || currentGroup?.assigned_driver_id || "";
|
||||||
const logPayload = driverId
|
const logPayload = driverId
|
||||||
? { orderGroupId, action: "driver_assigned", newValue: driverName, details: { driver_id: driverId, driver_name: driverName } }
|
{ orderGroupId, action: "driver_assigned", oldValue: oldDriverName || undefined, newValue: driverName, details: { driver_id: driverId, driver_name: driverName } }
|
||||||
: { orderGroupId, action: "driver_removed", oldValue: oldDriverName, newValue: "Снят", details: { driver_name: oldDriverName } };
|
: { orderGroupId, action: "driver_removed", oldValue: oldDriverName, newValue: "Снят", details: { driver_name: oldDriverName } };
|
||||||
await logAction(logPayload).catch(() => {});
|
await logAction(logPayload).catch(() => {});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue