Corp/frontend/src/components/users/EmployeeActionsTimeline.tsx

148 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { History, Pencil, Trash2 } from "lucide-react";
import {
getEmployeeActionTone,
} from "@/lib/users/userDetailUtils";
import type { EmployeeAction } from "@/types/api";
type EmployeeActionsTimelineProps = {
actionLoading: string | null;
canManageActions: boolean;
canViewActions: boolean;
latestActionId: number | null;
onAddAction: () => void;
onDeleteAction: (actionId: number) => void;
onEditAction: (action: EmployeeAction) => void;
sortedActions: EmployeeAction[];
};
function formatActionDate(value: string) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return date.toLocaleDateString("ru-RU", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
}
export default function EmployeeActionsTimeline({
actionLoading,
canManageActions,
canViewActions,
latestActionId,
onAddAction,
onDeleteAction,
onEditAction,
sortedActions,
}: EmployeeActionsTimelineProps) {
if (!canViewActions || sortedActions.length === 0) {
return null;
}
return (
<section className="app-surface rounded-[24px] p-5">
<div className="mb-4 flex items-start justify-between gap-4">
<div className="flex items-center gap-2">
<History size={18} className="app-text-muted" />
<h2 className="app-card-caption">Кадровые события</h2>
</div>
<div className="flex items-center gap-2">
<span className="app-text-muted text-sm">
{sortedActions.length}{" "}
{sortedActions.length === 1
? "событие"
: sortedActions.length < 5
? "события"
: "событий"}
</span>
{canManageActions ? (
<button
onClick={onAddAction}
className="app-action-secondary rounded-xl px-3 py-2 text-sm font-medium"
>
+ Добавить
</button>
) : null}
</div>
</div>
<div className="space-y-3">
{sortedActions.map((action) => {
const isCurrent = latestActionId === action.id;
const deleteKey = `delete-${action.id}`;
const tone = getEmployeeActionTone(action.action);
return (
<div key={action.id} className="relative pl-5">
<span
className="absolute bottom-0 left-1.5 top-0 w-px"
style={{ backgroundColor: tone.lineColor }}
/>
{isCurrent ? (
<span
className="absolute left-0 top-2 h-3.5 w-3.5 rounded-full border-4 border-[var(--surface-primary)]"
style={{ backgroundColor: tone.lineColor }}
/>
) : null}
<div className="app-surface-muted rounded-2xl px-4 py-3">
<div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<span
className={`app-status-pill ${tone.badgeClass}`}
>
{action.action_display || action.action}
</span>
{isCurrent ? (
<span className="app-text-muted text-xs font-medium">
текущий
</span>
) : null}
</div>
{action.comment ? (
<p className="app-text-wrap app-text-muted mt-2 text-sm leading-6">
{action.comment}
</p>
) : null}
</div>
<div className="flex shrink-0 items-start gap-2">
<time className="app-text-muted pt-1 text-sm">
{formatActionDate(action.date)}
</time>
{canManageActions ? (
<div className="flex gap-1">
<button
onClick={() => onEditAction(action)}
className="app-action-secondary inline-flex h-9 w-9 items-center justify-center rounded-xl"
title="Редактировать"
disabled={actionLoading === deleteKey}
>
<Pencil size={14} />
</button>
<button
onClick={() => onDeleteAction(action.id)}
className="app-action-secondary inline-flex h-9 w-9 items-center justify-center rounded-xl text-red-400 hover:border-red-400/40 hover:bg-red-500/10 hover:text-red-300"
title="Удалить"
disabled={actionLoading === deleteKey}
>
{actionLoading === deleteKey ? (
<div className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-red-400 border-t-transparent" />
) : (
<Trash2 size={14} />
)}
</button>
</div>
) : null}
</div>
</div>
</div>
</div>
);
})}
</div>
</section>
);
}