mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 06:40:57 +00:00
217 lines
8.3 KiB
TypeScript
217 lines
8.3 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/shared/common/ui/dialog";
|
|
import { Button } from "@/shared/common/ui/button";
|
|
import { Badge } from "@/shared/common/ui/badge";
|
|
import { AlertCircle, Trash2, Briefcase } from "lucide-react";
|
|
import { t } from "i18next";
|
|
import { useTranslation } from "react-i18next";
|
|
import { toast } from "sonner";
|
|
import { deleteEmployeePosition } from "../../services/api/employeePositionsService";
|
|
import { useLocalizedName } from "@/shared/common/localizedName";
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from "@/shared/common/ui/alert-dialog";
|
|
|
|
export interface EmployeePosition {
|
|
id: string;
|
|
employeeId: string;
|
|
position: {
|
|
id: string;
|
|
name: {
|
|
am: string;
|
|
en: string;
|
|
};
|
|
};
|
|
}
|
|
|
|
interface EmployeePositionsDialogProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
employeeName: string;
|
|
positions: EmployeePosition[];
|
|
onPositionRemoved?: () => void;
|
|
}
|
|
|
|
export const EmployeePositionsDialog: React.FC<
|
|
EmployeePositionsDialogProps
|
|
> = ({ isOpen, onClose, employeeName, positions, onPositionRemoved }) => {
|
|
const { t } = useTranslation();
|
|
const [loading, setLoading] = useState(false);
|
|
const [positionToDelete, setPositionToDelete] = useState<EmployeePosition | null>(null);
|
|
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
|
const localizedName = useLocalizedName();
|
|
|
|
const handleRemovePosition = async (position: EmployeePosition) => {
|
|
setLoading(true);
|
|
try {
|
|
await deleteEmployeePosition(position.id);
|
|
toast.success(t("positions.positionRemoved"), {
|
|
description: t("positions.employeeRemovedFromPosition", {
|
|
position: localizedName(position.position.name),
|
|
}),
|
|
duration: 4000,
|
|
});
|
|
setShowConfirmDialog(false);
|
|
setPositionToDelete(null);
|
|
|
|
// Call callback to refresh positions list
|
|
if (onPositionRemoved) {
|
|
onPositionRemoved();
|
|
}
|
|
} catch (error: unknown) {
|
|
const errorMessage =
|
|
error instanceof Error ? error.message : "Unknown error";
|
|
|
|
// Check if it's a "Referenced Entity does not exist" error
|
|
if (errorMessage.includes("Referenced Entity")) {
|
|
toast.error(t("positions.positionAlreadyRemoved"), {
|
|
description: t("positions.positionMayHaveBeenRemovedAlready"),
|
|
duration: 4000,
|
|
});
|
|
// Still close the dialog and refresh
|
|
setShowConfirmDialog(false);
|
|
setPositionToDelete(null);
|
|
if (onPositionRemoved) {
|
|
onPositionRemoved();
|
|
}
|
|
} else {
|
|
toast.error(t("positions.failedToRemovePosition"), {
|
|
description: errorMessage,
|
|
duration: 4000,
|
|
});
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleOpenConfirm = (position: EmployeePosition) => {
|
|
setPositionToDelete(position);
|
|
setShowConfirmDialog(true);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
<DialogContent className="sm:max-w-lg dark:bg-gray-800">
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-2 dark:text-white">
|
|
<Briefcase className="h-5 w-5 text-primary-600 dark:text-primary-400" />
|
|
{t("positions.employeePositions")}
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<div className="py-4 space-y-4">
|
|
{/* Employee Name */}
|
|
<div className="px-4 py-3 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600">
|
|
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">
|
|
{t("positions.employee")}
|
|
</p>
|
|
<p className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
|
{employeeName}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Positions List */}
|
|
{positions && positions.length > 0 ? (
|
|
<div className="space-y-3 max-h-96 overflow-y-auto">
|
|
{positions
|
|
.filter((position) => position && position.position && position.position.name)
|
|
.map((position) => (
|
|
<div
|
|
key={position.id}
|
|
className="flex items-center justify-between p-4 rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700/50 hover:border-gray-300 dark:hover:border-gray-500 transition-colors">
|
|
<div className="flex items-center gap-3 flex-1">
|
|
<div className="p-2 rounded-lg bg-primary-100 dark:bg-primary-900/40 text-primary-600 dark:text-primary-400">
|
|
<Briefcase className="h-4 w-4" />
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-sm font-medium text-gray-800 dark:text-gray-200 truncate">
|
|
{localizedName(position.position.name)}
|
|
</p>
|
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
{t("positions.positionId")}: {position.id.slice(0, 8)}...
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => handleOpenConfirm(position)}
|
|
className="ml-2 text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-900/20 dark:text-red-400 dark:hover:text-red-300"
|
|
disabled={loading}>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="flex flex-col items-center justify-center py-8 text-gray-500 dark:text-gray-400">
|
|
<AlertCircle className="h-12 w-12 mb-2 text-gray-300 dark:text-gray-600" />
|
|
<p className="text-sm font-medium">
|
|
{t("positions.noPositions")}
|
|
</p>
|
|
<p className="text-xs mt-1 text-gray-400 dark:text-gray-500">
|
|
{t("positions.employeeHasNoPositions")}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-2 pt-4 border-t border-gray-200 dark:border-gray-600">
|
|
<Button
|
|
variant="outline"
|
|
onClick={onClose}
|
|
className="dark:border-gray-600 dark:text-gray-200 dark:hover:bg-gray-700">
|
|
{t("common.close")}
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Confirmation Dialog for Deletion */}
|
|
<AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
|
|
<AlertDialogContent className="dark:bg-gray-800">
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle className="dark:text-white">
|
|
{t("positions.removeFromPosition")}
|
|
</AlertDialogTitle>
|
|
<AlertDialogDescription className="dark:text-gray-400">
|
|
{t("positions.confirmRemoveFromPosition", {
|
|
employee: employeeName,
|
|
position: positionToDelete
|
|
? localizedName(positionToDelete.position.name)
|
|
: "",
|
|
})}
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<div className="flex justify-end gap-3 pt-4">
|
|
<AlertDialogCancel className="dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 dark:border-gray-600">
|
|
{t("common.cancel")}
|
|
</AlertDialogCancel>
|
|
<AlertDialogAction
|
|
onClick={() =>
|
|
positionToDelete && handleRemovePosition(positionToDelete)
|
|
}
|
|
disabled={loading}
|
|
className="bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-600">
|
|
{loading ? t("common.removing") : t("positions.remove")}
|
|
</AlertDialogAction>
|
|
</div>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</>
|
|
);
|
|
};
|