Files
edr-platform/apps/edr-freight-web/backoffice/src/user-management/components/content/CommonRemarks.tsx
2026-07-10 10:41:48 +03:00

269 lines
9.4 KiB
TypeScript

import React, { useState } from "react";
import { Button } from "@/shared/common/ui/button";
import { Card } from "@/shared/common/ui/card";
import { Input } from "@/shared/common/ui/input";
import { Label } from "@radix-ui/react-label";
import { Plus, Trash2, Pencil } from "lucide-react";
import {
prefixSuffixService,
RemarkPayload,
} from "@/user-management/services/api/prefixSuffixService";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/shared/common/ui/dialog";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/shared/common/ui/alert-dialog";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useRemarkByUnitId } from "@/super-admin/hooks/useRemark";
import { t } from "i18next";
interface Props {
unitId: string;
}
interface RemarkDto {
id: string;
remark: string;
description?: string;
}
export const CommonRemarks = ({ unitId }: Props) => {
const [remark, setRemark] = useState("");
const [description, setDescription] = useState("");
const [editingId, setEditingId] = useState<string | null>(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [deleteRemarkId, setDeleteRemarkId] = useState<string | null>(null);
const [error, setError] = useState("");
const queryClient = useQueryClient();
const { data: remarksData, isLoading: isRemarkLoading } =
useRemarkByUnitId(unitId,{
skip: 0,
take: 300,
});
// Save/Edit Remark
const saveRemarkMutation = useMutation({
mutationFn: async () => {
if (!remark) throw new Error(t("contentManagement.remarkRequired"));
const payload: RemarkPayload = { unitId, remark, description };
if (editingId) {
return prefixSuffixService.editRemark(editingId, payload);
}
return prefixSuffixService.createRemark(payload);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["remark", unitId] });
resetForm();
setIsDialogOpen(false);
},
onError: (err: any) =>
setError(err.message || t("contentManagement.failed")),
});
// Delete Remark
const deleteRemarkMutation = useMutation({
mutationFn: (id: string) => prefixSuffixService.deleteRemarks(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["remark", unitId] });
setIsDeleteDialogOpen(false);
setDeleteRemarkId(null);
},
onError: () => setError(t("contentManagement.failedToDelete")),
});
const resetForm = () => {
setRemark("");
setDescription("");
setEditingId(null);
setError("");
};
const handleEdit = (item: RemarkDto) => {
setEditingId(item.id);
setRemark(item.remark);
setDescription(item.description || "");
setIsDialogOpen(true);
};
const handleSubmit = () => saveRemarkMutation.mutate();
return (
<Card className="p-4 border shadow-sm space-y-4 dark:border-gray-700 dark:bg-gray-800">
{/* Add/Edit Dialog */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button className="w-[180px] bg-primary hover:bg-primary/90 text-primary-foreground flex items-center">
<Plus className="h-4 w-4 mr-2" />
{t("contentManagement.addRemark")}
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-xl w-full dark:bg-gray-800">
<DialogHeader>
<DialogTitle className="dark:text-white">
{editingId
? t("contentManagement.editRemark")
: t("contentManagement.addRemark")}
</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="flex flex-col">
<Label htmlFor="remark" className="mb-1 dark:text-gray-200">
{t("header.Remark")}
</Label>
<Input
id="remark"
value={remark}
onChange={(e) => setRemark(e.target.value)}
placeholder={t("header.Remark")}
disabled={isRemarkLoading}
className="w-full dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
<Label htmlFor="description" className="mb-1 dark:text-gray-200">
{t("header.Description")}
</Label>
<Input
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder={t("header.Description")}
disabled={isRemarkLoading}
className="w-full mb-2 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
</div>
<div className="flex justify-end gap-2 pt-4 border-t border-gray-200 dark:border-gray-700">
<Button
variant="outline"
onClick={() => {
resetForm();
setIsDialogOpen(false);
}}>
{t("common.Cancel")}
</Button>
<Button
onClick={handleSubmit}
disabled={isRemarkLoading} // ✅ access via mutation object
className="bg-primary hover:bg-primary/90 text-primary-foreground">
{isRemarkLoading
? t("contentManagement.saving")
: editingId
? t("delegation.update")
: t("delegation.save")}
</Button>
</div>
</DialogContent>
</Dialog>
{/* Remarks Table */}
<h3 className="font-semibold text-md dark:text-white">
{t("contentManagement.commonRemarks")}
</h3>
{isRemarkLoading ? (
<div className="text-center py-4 dark:text-gray-400">{t("contentManagement.loading")}</div>
) : (
<div className="overflow-x-auto max-h-[380px] overflow-y-auto border rounded dark:border-gray-600">
<table className="min-w-full text-sm text-left">
<thead>
<tr className="border-b bg-gray-100 dark:bg-gray-700">
<th className="px-3 py-2 dark:text-white">#</th>
<th className="px-3 py-2 dark:text-white">{t("header.Remark")}</th>
<th className="px-3 py-2 dark:text-white">{t("header.Description")}</th>
<th className="px-3 py-2 dark:text-white">{t("userRecord.Actions")}</th>
</tr>
</thead>
<tbody>
{remarksData?.items.length ? (
remarksData.items.map((item: any, idx: any) => (
<tr key={item.id} className="border-b hover:bg-gray-50 dark:hover:bg-gray-700 dark:border-gray-600">
<td className="px-3 py-2 dark:text-gray-300">{idx + 1}</td>
<td className="px-3 py-2 dark:text-gray-300">{item.remark}</td>
<td className="px-3 py-2 dark:text-gray-300">{item.description}</td>
<td className="px-3 py-2 flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleEdit(item)}>
<Pencil className="h-4 w-4" />
</Button>
<Button
variant="destructive"
size="sm"
onClick={() => {
setDeleteRemarkId(item.id);
setIsDeleteDialogOpen(true);
}}>
<Trash2 className="h-4 w-4" />
</Button>
</td>
</tr>
))
) : (
<tr>
<td colSpan={4} className="text-center py-4 text-gray-500 dark:text-gray-400">
{t("contentManagement.noRec")}
</td>
</tr>
)}
</tbody>
</table>
</div>
)}
<AlertDialog
open={isDeleteDialogOpen}
onOpenChange={(open) => {
if (deleteRemarkMutation.isPending) return;
setIsDeleteDialogOpen(open);
if (!open) setDeleteRemarkId(null);
}}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t("common.delete")}</AlertDialogTitle>
<AlertDialogDescription>
{t("contentManagement.deleteMsg")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deleteRemarkMutation.isPending}>
{t("common.cancel")}
</AlertDialogCancel>
<AlertDialogAction
className="bg-red-600 hover:bg-red-700"
disabled={deleteRemarkMutation.isPending || !deleteRemarkId}
onClick={() => {
if (!deleteRemarkId) return;
deleteRemarkMutation.mutate(deleteRemarkId);
}}
>
{deleteRemarkMutation.isPending
? t("common.deleting", "Deleting...")
: t("common.delete")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Card>
);
};