776 lines
27 KiB
TypeScript
776 lines
27 KiB
TypeScript
"use client";
|
|
import React, { useState, useEffect } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
import { ChevronDownIcon, ChevronUpIcon, SaveIcon, EyeIcon, RotateCcwIcon, UsersIcon, HierarchyIcon, PlusIcon, TrashIcon } from "@/components/icons";
|
|
import { FormField } from "./common/FormField";
|
|
import { DynamicArray } from "./common/DynamicArray";
|
|
import {
|
|
UserLevelsCreateRequest,
|
|
UserLevel,
|
|
Province,
|
|
createUserLevel,
|
|
getUserLevels,
|
|
getProvinces,
|
|
} from "@/service/approval-workflows";
|
|
import Swal from "sweetalert2";
|
|
|
|
interface UserLevelsFormProps {
|
|
initialData?: UserLevelsCreateRequest;
|
|
onSave?: (data: UserLevelsCreateRequest) => void;
|
|
onCancel?: () => void;
|
|
isLoading?: boolean;
|
|
mode?: "single" | "bulk";
|
|
}
|
|
|
|
export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
|
initialData,
|
|
onSave,
|
|
onCancel,
|
|
isLoading = false,
|
|
mode = "single",
|
|
}) => {
|
|
// Form state
|
|
const [formData, setFormData] = useState<UserLevelsCreateRequest>({
|
|
name: "",
|
|
aliasName: "",
|
|
levelNumber: 1,
|
|
parentLevelId: undefined,
|
|
provinceId: undefined,
|
|
group: "",
|
|
isApprovalActive: true,
|
|
isActive: true,
|
|
});
|
|
|
|
const [bulkFormData, setBulkFormData] = useState<UserLevelsCreateRequest[]>([]);
|
|
const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
|
|
const [provinces, setProvinces] = useState<Province[]>([]);
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const [expandedHierarchy, setExpandedHierarchy] = useState(false);
|
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
|
const [activeTab, setActiveTab] = useState(mode === "single" ? "basic" : "bulk");
|
|
|
|
useEffect(() => {
|
|
if (initialData) {
|
|
setFormData(initialData);
|
|
}
|
|
}, [initialData]);
|
|
|
|
useEffect(() => {
|
|
const loadData = async () => {
|
|
try {
|
|
const [userLevelsRes, provincesRes] = await Promise.all([
|
|
getUserLevels(),
|
|
getProvinces(),
|
|
]);
|
|
|
|
if (!userLevelsRes?.error) setUserLevels(userLevelsRes?.data?.data || []);
|
|
if (!provincesRes?.error) setProvinces(provincesRes?.data?.data || []);
|
|
} catch (error) {
|
|
console.error("Error loading form data:", error);
|
|
} finally {
|
|
setIsLoadingData(false);
|
|
}
|
|
};
|
|
|
|
loadData();
|
|
}, []);
|
|
|
|
const validateForm = (data: UserLevelsCreateRequest): Record<string, string> => {
|
|
const newErrors: Record<string, string> = {};
|
|
|
|
if (!data.name.trim()) {
|
|
newErrors.name = "Level name is required";
|
|
} else if (data.name.trim().length < 3) {
|
|
newErrors.name = "Level name must be at least 3 characters";
|
|
}
|
|
|
|
if (!data.aliasName.trim()) {
|
|
newErrors.aliasName = "Alias name is required";
|
|
} else if (data.aliasName.trim().length < 3) {
|
|
newErrors.aliasName = "Alias name must be at least 3 characters";
|
|
}
|
|
|
|
if (!data.levelNumber || data.levelNumber <= 0) {
|
|
newErrors.levelNumber = "Level number must be a positive integer";
|
|
}
|
|
|
|
// Check for duplicate level numbers
|
|
// const existingLevel = userLevels.length > 0 ? userLevels.find(level => level.levelNumber === data.levelNumber) : null;
|
|
// if (existingLevel && (!initialData || existingLevel.id !== (initialData as any).id)) {
|
|
// newErrors.levelNumber = "Level number already exists";
|
|
// }
|
|
|
|
return newErrors;
|
|
};
|
|
|
|
const validateBulkForm = (): boolean => {
|
|
let isValid = true;
|
|
const newErrors: Record<string, string> = {};
|
|
|
|
bulkFormData.forEach((data, index) => {
|
|
const itemErrors = validateForm(data);
|
|
Object.keys(itemErrors).forEach(key => {
|
|
newErrors[`${index}.${key}`] = itemErrors[key];
|
|
});
|
|
if (Object.keys(itemErrors).length > 0) {
|
|
isValid = false;
|
|
}
|
|
});
|
|
|
|
setErrors(newErrors);
|
|
return isValid;
|
|
};
|
|
|
|
const handleFieldChange = (field: keyof UserLevelsCreateRequest, value: any) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }));
|
|
if (errors[field]) {
|
|
setErrors(prev => ({ ...prev, [field]: "" }));
|
|
}
|
|
};
|
|
|
|
const handleBulkFieldChange = (index: number, field: keyof UserLevelsCreateRequest, value: any) => {
|
|
setBulkFormData((prev: UserLevelsCreateRequest[]) => {
|
|
const newData = [...prev];
|
|
newData[index] = { ...newData[index], [field]: value };
|
|
return newData;
|
|
});
|
|
};
|
|
|
|
const handleTabChange = (value: string) => {
|
|
setActiveTab(value);
|
|
if (value === "bulk") {
|
|
} else {
|
|
}
|
|
};
|
|
|
|
const addBulkItem = () => {
|
|
const newItem: UserLevelsCreateRequest = {
|
|
name: "",
|
|
aliasName: "",
|
|
levelNumber: 1,
|
|
parentLevelId: undefined,
|
|
provinceId: undefined,
|
|
group: "",
|
|
isApprovalActive: true,
|
|
isActive: true,
|
|
};
|
|
setBulkFormData(prev => [...prev, newItem]);
|
|
};
|
|
|
|
const renderBulkItemForm = (item: UserLevelsCreateRequest, index: number, onUpdate: (item: UserLevelsCreateRequest) => void, onDelete: () => void) => {
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<FormField
|
|
label="Level Name"
|
|
name={`name-${index}`}
|
|
type="text"
|
|
placeholder="e.g., Senior Editor, Manager, Publisher"
|
|
value={item.name}
|
|
onChange={(value) => onUpdate({ ...item, name: value })}
|
|
error={errors[`${index}.name`]}
|
|
required
|
|
/>
|
|
|
|
<FormField
|
|
label="Alias Name"
|
|
name={`aliasName-${index}`}
|
|
type="text"
|
|
placeholder="e.g., SENIOR_EDITOR, MANAGER, PUBLISHER"
|
|
value={item.aliasName}
|
|
onChange={(value) => onUpdate({ ...item, aliasName: value.toUpperCase() })}
|
|
error={errors[`${index}.aliasName`]}
|
|
required
|
|
helpText="Short identifier for system use"
|
|
/>
|
|
|
|
<FormField
|
|
label="Level Number"
|
|
name={`levelNumber-${index}`}
|
|
type="number"
|
|
placeholder="e.g., 1, 2, 3"
|
|
value={item.levelNumber}
|
|
onChange={(value) => onUpdate({ ...item, levelNumber: value ? Number(value) : 1 })}
|
|
error={errors[`${index}.levelNumber`]}
|
|
required
|
|
min={1}
|
|
helpText="Higher number = higher authority"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormField
|
|
label="Parent Level"
|
|
name={`parentLevelId-${index}`}
|
|
type="select"
|
|
placeholder={userLevels.length > 0 ? "Select parent level" : "No parent levels available"}
|
|
value={item.parentLevelId}
|
|
onChange={(value) => onUpdate({ ...item, parentLevelId: value !== undefined ? Number(value) : undefined })}
|
|
options={[
|
|
{ value: 0, label: "No Parent (Root Level)" },
|
|
...(userLevels.length > 0 ? userLevels.map(level => ({
|
|
value: level.id,
|
|
label: `${level.name} (Level ${level.levelNumber})`,
|
|
})) : [])
|
|
]}
|
|
helpText={userLevels.length === 0 ? "No parent levels found. This will be a root level." : "Select parent level for hierarchy"}
|
|
disabled={userLevels.length === 0}
|
|
/>
|
|
|
|
<FormField
|
|
label="Province"
|
|
name={`provinceId-${index}`}
|
|
type="select"
|
|
placeholder={provinces.length > 0 ? "Select province" : "No provinces available"}
|
|
value={item.provinceId}
|
|
onChange={(value) => onUpdate({ ...item, provinceId: value ? Number(value) : undefined })}
|
|
options={provinces.length > 0 ? provinces.map(province => ({
|
|
value: province.id,
|
|
label: province.prov_name,
|
|
})) : []}
|
|
helpText={provinces.length === 0 ? "No provinces found. Please ensure provinces are available in the system." : "Geographic scope for this level"}
|
|
disabled={provinces.length === 0}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<FormField
|
|
label="Group"
|
|
name={`group-${index}`}
|
|
type="text"
|
|
placeholder="e.g., Editorial, Management, Technical"
|
|
value={item.group || ""}
|
|
onChange={(value) => onUpdate({ ...item, group: value })}
|
|
helpText="Group classification for organization"
|
|
/>
|
|
|
|
<FormField
|
|
label="Is Approval Active"
|
|
name={`isApprovalActive-${index}`}
|
|
type="checkbox"
|
|
value={item.isApprovalActive}
|
|
onChange={(value) => onUpdate({ ...item, isApprovalActive: value })}
|
|
helpText="Users with this level can participate in approval process"
|
|
/>
|
|
|
|
<FormField
|
|
label="Is Active"
|
|
name={`isActive-${index}`}
|
|
type="checkbox"
|
|
value={item.isActive}
|
|
onChange={(value) => onUpdate({ ...item, isActive: value })}
|
|
helpText="Level is available for assignment"
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
// Determine current mode based on active tab
|
|
const currentMode = activeTab === "bulk" ? "bulk" : "single";
|
|
|
|
if (currentMode === "single") {
|
|
const validationErrors = validateForm(formData);
|
|
console.log("Form mode: ", currentMode);
|
|
console.log("Error single ", validationErrors);
|
|
|
|
if (Object.keys(validationErrors).length > 0) {
|
|
setErrors(validationErrors);
|
|
Swal.fire({
|
|
title: "Validation Error",
|
|
text: "Please fix the errors before submitting",
|
|
icon: "error",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: 'swal-z-index-9999'
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
} else {
|
|
if (!validateBulkForm()) {
|
|
Swal.fire({
|
|
title: "Validation Error",
|
|
text: "Please fix the errors before submitting",
|
|
icon: "error",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: 'swal-z-index-9999'
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
|
|
try {
|
|
if (onSave) {
|
|
if (currentMode === "single") {
|
|
onSave(formData);
|
|
} else {
|
|
// For bulk mode, save each item individually
|
|
let hasErrors = false;
|
|
let successCount = 0;
|
|
|
|
for (const item of bulkFormData) {
|
|
const response = await createUserLevel(item);
|
|
|
|
if (response?.error) {
|
|
hasErrors = true;
|
|
Swal.fire({
|
|
title: "Error",
|
|
text: `Failed to create user level "${item.name}": ${response?.message || "Unknown error"}`,
|
|
icon: "error",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: 'swal-z-index-9999'
|
|
}
|
|
});
|
|
} else {
|
|
successCount++;
|
|
Swal.fire({
|
|
title: "Success",
|
|
text: `User level "${item.name}" created successfully`,
|
|
icon: "success",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: 'swal-z-index-9999'
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Refresh page if at least one item was created successfully
|
|
if (successCount > 0) {
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 1000); // Small delay to let user see the success message
|
|
}
|
|
}
|
|
} else {
|
|
if (currentMode === "single") {
|
|
const response = await createUserLevel(formData);
|
|
console.log("Create Response: ", response);
|
|
|
|
if (response?.error) {
|
|
Swal.fire({
|
|
title: "Error",
|
|
text: response?.message || "Failed to create user level",
|
|
icon: "error",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: 'swal-z-index-9999'
|
|
}
|
|
});
|
|
} else {
|
|
Swal.fire({
|
|
title: "Success",
|
|
text: "User level created successfully",
|
|
icon: "success",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: 'swal-z-index-9999'
|
|
}
|
|
}).then(() => {
|
|
// Refresh page after successful creation
|
|
window.location.reload();
|
|
});
|
|
}
|
|
} else {
|
|
// Bulk creation
|
|
const promises = bulkFormData.map(item => createUserLevel(item));
|
|
const responses = await Promise.all(promises);
|
|
|
|
console.log("Create Responses: ", responses);
|
|
|
|
const failedCount = responses.filter((r: any) => r.error).length;
|
|
const successCount = responses.length - failedCount;
|
|
|
|
if (failedCount === 0) {
|
|
Swal.fire({
|
|
title: "Success",
|
|
text: `All ${successCount} user levels created successfully`,
|
|
icon: "success",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: 'swal-z-index-9999'
|
|
}
|
|
});
|
|
} else {
|
|
Swal.fire({
|
|
title: "Partial Success",
|
|
text: `${successCount} user levels created successfully, ${failedCount} failed`,
|
|
icon: "warning",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: 'swal-z-index-9999'
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Error submitting form:", error);
|
|
Swal.fire({
|
|
title: "Error",
|
|
text: "An unexpected error occurred",
|
|
icon: "error",
|
|
confirmButtonText: "OK",
|
|
customClass: {
|
|
popup: 'swal-z-index-9999'
|
|
}
|
|
});
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const handleReset = () => {
|
|
Swal.fire({
|
|
title: "Reset Form",
|
|
text: "Are you sure you want to reset all form data?",
|
|
icon: "warning",
|
|
showCancelButton: true,
|
|
confirmButtonText: "Yes, reset",
|
|
cancelButtonText: "Cancel",
|
|
customClass: {
|
|
popup: 'swal-z-index-9999'
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
if (mode === "single") {
|
|
setFormData({
|
|
name: "",
|
|
aliasName: "",
|
|
levelNumber: 1,
|
|
parentLevelId: undefined,
|
|
provinceId: undefined,
|
|
group: "",
|
|
isApprovalActive: true,
|
|
isActive: true,
|
|
});
|
|
} else {
|
|
setBulkFormData([]);
|
|
}
|
|
setErrors({});
|
|
}
|
|
});
|
|
};
|
|
|
|
const renderHierarchyTree = () => {
|
|
const buildTree = (levels: UserLevel[], parentId?: number): UserLevel[] => {
|
|
return levels
|
|
.filter(level => level.parentLevelId === parentId)
|
|
.map(level => ({
|
|
...level,
|
|
children: buildTree(levels, level.id)
|
|
}));
|
|
};
|
|
|
|
const tree = buildTree(userLevels);
|
|
|
|
const renderNode = (node: UserLevel & { children?: UserLevel[] }, depth = 0) => (
|
|
<div key={node.id} className="ml-4">
|
|
<div className="flex items-center gap-2 py-1">
|
|
<div className="w-4 h-4 border-l border-b border-gray-300"></div>
|
|
<span className="text-sm">
|
|
{node.name} (Level {node.levelNumber})
|
|
{node.group && <span className="text-gray-500 ml-2">- {node.group}</span>}
|
|
</span>
|
|
</div>
|
|
{node.children?.map((child: any) => renderNode(child, depth + 1))}
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="space-y-1">
|
|
{tree.map(node => renderNode(node))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
{isLoadingData && (
|
|
<div className="flex items-center justify-center py-8">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
<span className="ml-2 text-gray-600">Loading form data...</span>
|
|
</div>
|
|
)}
|
|
|
|
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
|
|
<TabsList className="grid w-full grid-cols-3">
|
|
<TabsTrigger value="basic" disabled={isLoadingData}>Basic Information</TabsTrigger>
|
|
{/* <TabsTrigger value="hierarchy" disabled={isLoadingData}>Hierarchy</TabsTrigger> */}
|
|
<TabsTrigger value="bulk" disabled={isLoadingData}>Bulk Operations</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* Basic Information Tab */}
|
|
<TabsContent value="basic" className="space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>User Level Basic Information</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<FormField
|
|
label="Level Name"
|
|
name="name"
|
|
type="text"
|
|
placeholder="e.g., Senior Editor, Manager, Publisher"
|
|
value={formData.name}
|
|
onChange={(value) => handleFieldChange("name", value)}
|
|
error={errors.name}
|
|
required
|
|
/>
|
|
|
|
<FormField
|
|
label="Alias Name"
|
|
name="aliasName"
|
|
type="text"
|
|
placeholder="e.g., SENIOR_EDITOR, MANAGER, PUBLISHER"
|
|
value={formData.aliasName}
|
|
onChange={(value) => handleFieldChange("aliasName", value.toUpperCase())}
|
|
error={errors.aliasName}
|
|
required
|
|
helpText="Short identifier for system use"
|
|
/>
|
|
|
|
<FormField
|
|
label="Level Number"
|
|
name="levelNumber"
|
|
type="number"
|
|
placeholder="e.g., 1, 2, 3"
|
|
value={formData.levelNumber}
|
|
onChange={(value) => handleFieldChange("levelNumber", value ? Number(value) : 1)}
|
|
error={errors.levelNumber}
|
|
required
|
|
min={1}
|
|
helpText="Higher number = higher authority"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormField
|
|
label="Parent Level"
|
|
name="parentLevelId"
|
|
type="select"
|
|
placeholder={userLevels.length > 0 ? "Select parent level" : "No parent levels available"}
|
|
value={formData.parentLevelId}
|
|
onChange={(value) => handleFieldChange("parentLevelId", value !== undefined ? Number(value) : undefined)}
|
|
options={[
|
|
{ value: 0, label: "No Parent (Root Level)" },
|
|
...(userLevels.length > 0 ? userLevels.map(level => ({
|
|
value: level.id,
|
|
label: `${level.name} (Level ${level.levelNumber})`,
|
|
})) : [])
|
|
]}
|
|
helpText={userLevels.length === 0 ? "No parent levels found. This will be a root level." : "Select parent level for hierarchy"}
|
|
disabled={userLevels.length === 0}
|
|
/>
|
|
|
|
<FormField
|
|
label="Province"
|
|
name="provinceId"
|
|
type="select"
|
|
placeholder={provinces.length > 0 ? "Select province" : "No provinces available"}
|
|
value={formData.provinceId}
|
|
onChange={(value) => handleFieldChange("provinceId", value ? Number(value) : undefined)}
|
|
options={provinces.length > 0 ? provinces.map(province => ({
|
|
value: province.id,
|
|
label: province.prov_name,
|
|
})) : []}
|
|
helpText={provinces.length === 0 ? "No provinces found. Please ensure provinces are available in the system." : "Geographic scope for this level"}
|
|
disabled={provinces.length === 0}
|
|
/>
|
|
</div>
|
|
|
|
<FormField
|
|
label="Group"
|
|
name="group"
|
|
type="text"
|
|
placeholder="e.g., Editorial, Management, Technical"
|
|
value={formData.group || ""}
|
|
onChange={(value) => handleFieldChange("group", value)}
|
|
helpText="Group classification for organization"
|
|
/>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormField
|
|
label="Is Approval Active"
|
|
name="isApprovalActive"
|
|
type="checkbox"
|
|
value={formData.isApprovalActive}
|
|
onChange={(value) => handleFieldChange("isApprovalActive", value)}
|
|
helpText="Users with this level can participate in approval process"
|
|
/>
|
|
|
|
<FormField
|
|
label="Is Active"
|
|
name="isActive"
|
|
type="checkbox"
|
|
value={formData.isActive}
|
|
onChange={(value) => handleFieldChange("isActive", value)}
|
|
helpText="Level is available for assignment"
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* Hierarchy Tab */}
|
|
{/* <TabsContent value="hierarchy" className="space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<HierarchyIcon className="h-5 w-5" />
|
|
Level Hierarchy Visualization
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Collapsible>
|
|
<CollapsibleTrigger asChild>
|
|
<Button variant="ghost" className="flex items-center gap-2">
|
|
{expandedHierarchy ? "Hide" : "Show"} Hierarchy Tree
|
|
{expandedHierarchy ? <ChevronUpIcon className="h-4 w-4" /> : <ChevronDownIcon className="h-4 w-4" />}
|
|
</Button>
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent className="mt-4">
|
|
{userLevels.length > 0 ? (
|
|
<div className="border rounded-lg p-4 bg-gray-50">
|
|
{renderHierarchyTree()}
|
|
</div>
|
|
) : (
|
|
<p className="text-gray-500 text-center py-8">No user levels found</p>
|
|
)}
|
|
</CollapsibleContent>
|
|
</Collapsible>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent> */}
|
|
|
|
{/* Bulk Operations Tab */}
|
|
<TabsContent value="bulk" className="space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<UsersIcon className="h-5 w-5" />
|
|
Bulk User Level Creation
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-lg font-medium">User Levels</h3>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={addBulkItem}
|
|
className="flex items-center gap-2"
|
|
>
|
|
<PlusIcon className="h-4 w-4" />
|
|
Add User Level
|
|
</Button>
|
|
</div>
|
|
|
|
{bulkFormData.length === 0 ? (
|
|
<div className="text-center py-8 text-gray-500">
|
|
<UsersIcon className="h-12 w-12 mx-auto mb-4 text-gray-400" />
|
|
<p>No user levels added yet. Add multiple levels to create them in bulk.</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
{bulkFormData.map((item, index) => (
|
|
<Card key={index} className="border-l-4 border-l-blue-500">
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-center justify-between">
|
|
<CardTitle className="text-base">
|
|
User Level #{index + 1}
|
|
</CardTitle>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => {
|
|
const newItems = bulkFormData.filter((_, i) => i !== index);
|
|
setBulkFormData(newItems);
|
|
}}
|
|
className="text-red-600 hover:text-red-700"
|
|
>
|
|
<TrashIcon className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{renderBulkItemForm(
|
|
item,
|
|
index,
|
|
(updatedItem) => {
|
|
const newItems = [...bulkFormData];
|
|
newItems[index] = updatedItem;
|
|
setBulkFormData(newItems);
|
|
},
|
|
() => {
|
|
const newItems = bulkFormData.filter((_, i) => i !== index);
|
|
setBulkFormData(newItems);
|
|
}
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
{/* Form Actions */}
|
|
<div className="flex items-center justify-between pt-6 border-t">
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={handleReset}
|
|
disabled={isSubmitting}
|
|
>
|
|
<RotateCcwIcon className="h-4 w-4 mr-2" />
|
|
Reset
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
{onCancel && (
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={onCancel}
|
|
disabled={isSubmitting}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
)}
|
|
|
|
<Button
|
|
type="submit"
|
|
disabled={isSubmitting || isLoading || isLoadingData}
|
|
className="flex items-center gap-2"
|
|
>
|
|
<SaveIcon className="h-4 w-4" />
|
|
{isSubmitting ? "Saving..." : isLoadingData ? "Loading..." : `Save ${activeTab === "bulk" ? "User Levels" : "User Level"}`}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
);
|
|
};
|