feat: update tenant - user level
This commit is contained in:
parent
f7475d16fd
commit
957d3c4e98
|
|
@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||||
import { PlusIcon, SettingsIcon, UsersIcon, WorkflowIcon } from "@/components/icons";
|
import { PlusIcon, SettingsIcon, UsersIcon, WorkflowIcon, DotsIcon, DeleteIcon } from "@/components/icons";
|
||||||
import { ApprovalWorkflowForm } from "@/components/form/ApprovalWorkflowForm";
|
import { ApprovalWorkflowForm } from "@/components/form/ApprovalWorkflowForm";
|
||||||
import { UserLevelsForm } from "@/components/form/UserLevelsForm";
|
import { UserLevelsForm } from "@/components/form/UserLevelsForm";
|
||||||
import { useWorkflowModal } from "@/components/modals/WorkflowModalProvider";
|
import { useWorkflowModal } from "@/components/modals/WorkflowModalProvider";
|
||||||
|
|
@ -26,6 +26,7 @@ function TenantSettingsContent() {
|
||||||
const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
|
const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isEditingWorkflow, setIsEditingWorkflow] = useState(false);
|
const [isEditingWorkflow, setIsEditingWorkflow] = useState(false);
|
||||||
|
const [editingUserLevel, setEditingUserLevel] = useState<UserLevel | null>(null);
|
||||||
const { checkWorkflowStatus } = useWorkflowStatusCheck();
|
const { checkWorkflowStatus } = useWorkflowStatusCheck();
|
||||||
const { showWorkflowModal } = useWorkflowModal();
|
const { showWorkflowModal } = useWorkflowModal();
|
||||||
|
|
||||||
|
|
@ -79,9 +80,27 @@ function TenantSettingsContent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsUserLevelDialogOpen(false);
|
setIsUserLevelDialogOpen(false);
|
||||||
|
setEditingUserLevel(null);
|
||||||
await loadData(); // Reload data after saving
|
await loadData(); // Reload data after saving
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEditUserLevel = (userLevel: UserLevel) => {
|
||||||
|
setEditingUserLevel(userLevel);
|
||||||
|
setIsUserLevelDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteUserLevel = async (userLevel: UserLevel) => {
|
||||||
|
if (window.confirm(`Are you sure you want to delete "${userLevel.name}"? This action cannot be undone.`)) {
|
||||||
|
try {
|
||||||
|
// TODO: Implement delete API call
|
||||||
|
console.log("Delete user level:", userLevel.id);
|
||||||
|
await loadData(); // Reload data after deletion
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting user level:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleBulkUserLevelSave = async (data: UserLevelsCreateRequest[]) => {
|
const handleBulkUserLevelSave = async (data: UserLevelsCreateRequest[]) => {
|
||||||
setIsUserLevelDialogOpen(false);
|
setIsUserLevelDialogOpen(false);
|
||||||
await loadData(); // Reload data after saving
|
await loadData(); // Reload data after saving
|
||||||
|
|
@ -186,6 +205,7 @@ function TenantSettingsContent() {
|
||||||
skipApprovalFor: workflow.clientSettings.skipApprovalFor || []
|
skipApprovalFor: workflow.clientSettings.skipApprovalFor || []
|
||||||
}
|
}
|
||||||
} : undefined}
|
} : undefined}
|
||||||
|
workflowId={workflow?.workflow.id}
|
||||||
onSave={handleWorkflowSave}
|
onSave={handleWorkflowSave}
|
||||||
onCancel={() => setIsEditingWorkflow(false)}
|
onCancel={() => setIsEditingWorkflow(false)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -428,12 +448,27 @@ function TenantSettingsContent() {
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="md:max-w-6xl max-h-[90vh] overflow-y-auto">
|
<DialogContent className="md:max-w-6xl max-h-[90vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Create New User Level</DialogTitle>
|
<DialogTitle>
|
||||||
|
{editingUserLevel ? `Edit User Level: ${editingUserLevel.name}` : "Create New User Level"}
|
||||||
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<UserLevelsForm
|
<UserLevelsForm
|
||||||
mode="single"
|
mode="single"
|
||||||
|
initialData={editingUserLevel ? {
|
||||||
|
name: editingUserLevel.name,
|
||||||
|
aliasName: editingUserLevel.aliasName,
|
||||||
|
levelNumber: editingUserLevel.levelNumber,
|
||||||
|
parentLevelId: editingUserLevel.parentLevelId || 0,
|
||||||
|
provinceId: editingUserLevel.provinceId,
|
||||||
|
group: editingUserLevel.group || "",
|
||||||
|
isApprovalActive: editingUserLevel.isApprovalActive,
|
||||||
|
isActive: editingUserLevel.isActive,
|
||||||
|
} : undefined}
|
||||||
onSave={handleUserLevelSave}
|
onSave={handleUserLevelSave}
|
||||||
onCancel={() => setIsUserLevelDialogOpen(false)}
|
onCancel={() => {
|
||||||
|
setIsUserLevelDialogOpen(false);
|
||||||
|
setEditingUserLevel(null);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
@ -509,6 +544,26 @@ function TenantSettingsContent() {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleEditUserLevel(rootLevel)}
|
||||||
|
className="h-8 w-8 p-0 border-gray-300 hover:border-blue-500 hover:bg-blue-50"
|
||||||
|
title="Edit User Level"
|
||||||
|
>
|
||||||
|
<DotsIcon className="h-4 w-4 text-gray-600 hover:text-blue-600" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleDeleteUserLevel(rootLevel)}
|
||||||
|
className="h-8 w-8 p-0 border-gray-300 hover:border-red-500 hover:bg-red-50"
|
||||||
|
title="Delete User Level"
|
||||||
|
>
|
||||||
|
<DeleteIcon className="h-4 w-4 text-gray-600 hover:text-red-600" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Child Levels */}
|
{/* Child Levels */}
|
||||||
|
|
@ -538,6 +593,26 @@ function TenantSettingsContent() {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleEditUserLevel(childLevel)}
|
||||||
|
className="h-7 w-7 p-0 border-gray-300 hover:border-blue-500 hover:bg-blue-50"
|
||||||
|
title="Edit User Level"
|
||||||
|
>
|
||||||
|
<DotsIcon className="h-3.5 w-3.5 text-gray-600 hover:text-blue-600" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleDeleteUserLevel(childLevel)}
|
||||||
|
className="h-7 w-7 p-0 border-gray-300 hover:border-red-500 hover:bg-red-50"
|
||||||
|
title="Delete User Level"
|
||||||
|
>
|
||||||
|
<DeleteIcon className="h-3.5 w-3.5 text-gray-600 hover:text-red-600" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -547,7 +622,7 @@ function TenantSettingsContent() {
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
{/* <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{userLevels.length > 0 ? userLevels.map((userLevel) => (
|
{userLevels.length > 0 ? userLevels.map((userLevel) => (
|
||||||
<Card key={userLevel.id} className="hover:shadow-lg transition-shadow">
|
<Card key={userLevel.id} className="hover:shadow-lg transition-shadow">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|
@ -627,7 +702,7 @@ function TenantSettingsContent() {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)) : ''}
|
)) : ''}
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{userLevels.length === 0 && !isLoading && (
|
{userLevels.length === 0 && !isLoading && (
|
||||||
<Card>
|
<Card>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tw-animate-css";
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
/* SweetAlert2 z-index fix */
|
||||||
|
.swal-z-index-9999 {
|
||||||
|
z-index: 9999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { DynamicArray } from "./common/DynamicArray";
|
||||||
import { MultiSelect } from "./common/MultiSelect";
|
import { MultiSelect } from "./common/MultiSelect";
|
||||||
import {
|
import {
|
||||||
CreateApprovalWorkflowWithClientSettingsRequest,
|
CreateApprovalWorkflowWithClientSettingsRequest,
|
||||||
|
UpdateApprovalWorkflowWithClientSettingsRequest,
|
||||||
ApprovalWorkflowStepRequest,
|
ApprovalWorkflowStepRequest,
|
||||||
ClientApprovalSettingsRequest,
|
ClientApprovalSettingsRequest,
|
||||||
UserLevel,
|
UserLevel,
|
||||||
|
|
@ -16,6 +17,7 @@ import {
|
||||||
UserRole,
|
UserRole,
|
||||||
ArticleCategory,
|
ArticleCategory,
|
||||||
createApprovalWorkflowWithClientSettings,
|
createApprovalWorkflowWithClientSettings,
|
||||||
|
updateApprovalWorkflowWithClientSettings,
|
||||||
getUserLevels,
|
getUserLevels,
|
||||||
getUsers,
|
getUsers,
|
||||||
getUserRoles,
|
getUserRoles,
|
||||||
|
|
@ -25,6 +27,7 @@ import Swal from "sweetalert2";
|
||||||
|
|
||||||
interface ApprovalWorkflowFormProps {
|
interface ApprovalWorkflowFormProps {
|
||||||
initialData?: CreateApprovalWorkflowWithClientSettingsRequest;
|
initialData?: CreateApprovalWorkflowWithClientSettingsRequest;
|
||||||
|
workflowId?: number; // For update mode
|
||||||
onSave?: (data: CreateApprovalWorkflowWithClientSettingsRequest) => void;
|
onSave?: (data: CreateApprovalWorkflowWithClientSettingsRequest) => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
|
@ -32,6 +35,7 @@ interface ApprovalWorkflowFormProps {
|
||||||
|
|
||||||
export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
initialData,
|
initialData,
|
||||||
|
workflowId,
|
||||||
onSave,
|
onSave,
|
||||||
onCancel,
|
onCancel,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
|
@ -137,8 +141,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
|
|
||||||
if (!formData.description.trim()) {
|
if (!formData.description.trim()) {
|
||||||
newErrors.description = "Description is required";
|
newErrors.description = "Description is required";
|
||||||
} else if (formData.description.trim().length < 10) {
|
} else if (formData.description.trim().length < 3) {
|
||||||
newErrors.description = "Description must be at least 10 characters";
|
newErrors.description = "Description must be at least 3 characters";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData.steps.length === 0) {
|
if (formData.steps.length === 0) {
|
||||||
|
|
@ -338,7 +342,10 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
title: "Validation Error",
|
title: "Validation Error",
|
||||||
text: "Please fix the errors before submitting",
|
text: "Please fix the errors before submitting",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -358,31 +365,92 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
skipApprovalFor: []
|
skipApprovalFor: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitData = {
|
if (workflowId) {
|
||||||
...formData,
|
// Update mode
|
||||||
clientApprovalSettings: hardcodedClientSettings
|
const updateData: UpdateApprovalWorkflowWithClientSettingsRequest = {
|
||||||
};
|
workflowId,
|
||||||
|
name: formData.name,
|
||||||
|
description: formData.description,
|
||||||
|
isActive: formData.isActive || false,
|
||||||
|
isDefault: formData.isDefault || false,
|
||||||
|
steps: formData.steps.map(step => ({
|
||||||
|
...step,
|
||||||
|
branchName: step.stepName, // branchName should be same as stepName
|
||||||
|
})),
|
||||||
|
clientSettings: hardcodedClientSettings
|
||||||
|
};
|
||||||
|
|
||||||
console.log("Submit Data: ", submitData);
|
console.log("Update Data: ", updateData);
|
||||||
|
|
||||||
const response = await createApprovalWorkflowWithClientSettings(submitData);
|
const response = await updateApprovalWorkflowWithClientSettings(updateData);
|
||||||
|
|
||||||
console.log("Response: ", response);
|
console.log("Update Response: ", response);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: response?.message?.messages[0] || "Failed to create approval workflow",
|
text: response?.message?.messages?.[0] || "Failed to update approval workflow",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK",
|
||||||
});
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Success",
|
||||||
|
text: "Approval workflow updated successfully",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
// Call onSave to trigger parent refresh
|
||||||
|
if (onSave) {
|
||||||
|
onSave(formData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Swal.fire({
|
// Create mode
|
||||||
title: "Success",
|
const submitData = {
|
||||||
text: "Approval workflow created successfully",
|
...formData,
|
||||||
icon: "success",
|
clientApprovalSettings: hardcodedClientSettings
|
||||||
confirmButtonText: "OK"
|
};
|
||||||
});
|
|
||||||
|
console.log("Create Data: ", submitData);
|
||||||
|
|
||||||
|
const response = await createApprovalWorkflowWithClientSettings(submitData);
|
||||||
|
|
||||||
|
console.log("Create Response: ", response);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error",
|
||||||
|
text: response?.message?.messages?.[0] || "Failed to create approval workflow",
|
||||||
|
icon: "error",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Success",
|
||||||
|
text: "Approval workflow created successfully",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
// Call onSave to trigger parent refresh
|
||||||
|
if (onSave) {
|
||||||
|
onSave(submitData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error submitting form:", error);
|
console.error("Error submitting form:", error);
|
||||||
|
|
@ -390,7 +458,10 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: "An unexpected error occurred",
|
text: "An unexpected error occurred",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
|
|
@ -404,7 +475,10 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
icon: "warning",
|
icon: "warning",
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
confirmButtonText: "Yes, reset",
|
confirmButtonText: "Yes, reset",
|
||||||
cancelButtonText: "Cancel"
|
cancelButtonText: "Cancel",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
setFormData({
|
setFormData({
|
||||||
|
|
@ -617,7 +691,7 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<SaveIcon className="h-4 w-4" />
|
<SaveIcon className="h-4 w-4" />
|
||||||
{isSubmitting ? "Saving..." : isLoadingData ? "Loading..." : "Save Workflow"}
|
{isSubmitting ? "Saving..." : isLoadingData ? "Loading..." : workflowId ? "Update Workflow" : "Save Workflow"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||||
import { ChevronDownIcon, ChevronUpIcon, SaveIcon, EyeIcon, RotateCcwIcon, UsersIcon, HierarchyIcon } from "@/components/icons";
|
import { ChevronDownIcon, ChevronUpIcon, SaveIcon, EyeIcon, RotateCcwIcon, UsersIcon, HierarchyIcon, PlusIcon, TrashIcon } from "@/components/icons";
|
||||||
import { FormField } from "./common/FormField";
|
import { FormField } from "./common/FormField";
|
||||||
import { DynamicArray } from "./common/DynamicArray";
|
import { DynamicArray } from "./common/DynamicArray";
|
||||||
import {
|
import {
|
||||||
|
|
@ -56,6 +56,7 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [expandedHierarchy, setExpandedHierarchy] = useState(false);
|
const [expandedHierarchy, setExpandedHierarchy] = useState(false);
|
||||||
const [isLoadingData, setIsLoadingData] = useState(true);
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
|
const [activeTab, setActiveTab] = useState(mode === "single" ? "basic" : "bulk");
|
||||||
|
|
||||||
// Load initial data
|
// Load initial data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -148,8 +149,28 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBulkItemsChange = (items: UserLevelsCreateRequest[]) => {
|
const handleTabChange = (value: string) => {
|
||||||
setBulkFormData(items);
|
setActiveTab(value);
|
||||||
|
// Update mode based on active tab
|
||||||
|
if (value === "bulk") {
|
||||||
|
// Mode will be determined by activeTab in handleSubmit
|
||||||
|
} else {
|
||||||
|
// Mode will be determined by activeTab in handleSubmit
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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) => {
|
const renderBulkItemForm = (item: UserLevelsCreateRequest, index: number, onUpdate: (item: UserLevelsCreateRequest) => void, onDelete: () => void) => {
|
||||||
|
|
@ -264,9 +285,13 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (mode === "single") {
|
// Determine current mode based on active tab
|
||||||
|
const currentMode = activeTab === "bulk" ? "bulk" : "single";
|
||||||
|
|
||||||
|
if (currentMode === "single") {
|
||||||
const validationErrors = validateForm(formData);
|
const validationErrors = validateForm(formData);
|
||||||
console.log(validationErrors);
|
console.log("Form mode: ", currentMode);
|
||||||
|
console.log("Error single ", validationErrors);
|
||||||
|
|
||||||
if (Object.keys(validationErrors).length > 0) {
|
if (Object.keys(validationErrors).length > 0) {
|
||||||
setErrors(validationErrors);
|
setErrors(validationErrors);
|
||||||
|
|
@ -274,7 +299,10 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
title: "Validation Error",
|
title: "Validation Error",
|
||||||
text: "Please fix the errors before submitting",
|
text: "Please fix the errors before submitting",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +312,10 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
title: "Validation Error",
|
title: "Validation Error",
|
||||||
text: "Please fix the errors before submitting",
|
text: "Please fix the errors before submitting",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -294,31 +325,75 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (onSave) {
|
if (onSave) {
|
||||||
if (mode === "single") {
|
if (currentMode === "single") {
|
||||||
onSave(formData);
|
onSave(formData);
|
||||||
} else {
|
} else {
|
||||||
// For bulk mode, save each item individually
|
// For bulk mode, save each item individually
|
||||||
|
let hasErrors = false;
|
||||||
|
let successCount = 0;
|
||||||
|
|
||||||
for (const item of bulkFormData) {
|
for (const item of bulkFormData) {
|
||||||
await createUserLevel(item);
|
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 {
|
} else {
|
||||||
if (mode === "single") {
|
if (currentMode === "single") {
|
||||||
const response = await createUserLevel(formData);
|
const response = await createUserLevel(formData);
|
||||||
|
console.log("Create Response: ", response);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: response?.message || "Failed to create user level",
|
text: response?.message || "Failed to create user level",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
text: "User level created successfully",
|
text: "User level created successfully",
|
||||||
icon: "success",
|
icon: "success",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
// Refresh page after successful creation
|
||||||
|
window.location.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -326,6 +401,8 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
const promises = bulkFormData.map(item => createUserLevel(item));
|
const promises = bulkFormData.map(item => createUserLevel(item));
|
||||||
const responses = await Promise.all(promises);
|
const responses = await Promise.all(promises);
|
||||||
|
|
||||||
|
console.log("Create Responses: ", responses);
|
||||||
|
|
||||||
const failedCount = responses.filter((r: any) => r.error).length;
|
const failedCount = responses.filter((r: any) => r.error).length;
|
||||||
const successCount = responses.length - failedCount;
|
const successCount = responses.length - failedCount;
|
||||||
|
|
||||||
|
|
@ -334,14 +411,20 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
text: `All ${successCount} user levels created successfully`,
|
text: `All ${successCount} user levels created successfully`,
|
||||||
icon: "success",
|
icon: "success",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Partial Success",
|
title: "Partial Success",
|
||||||
text: `${successCount} user levels created successfully, ${failedCount} failed`,
|
text: `${successCount} user levels created successfully, ${failedCount} failed`,
|
||||||
icon: "warning",
|
icon: "warning",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +435,10 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: "An unexpected error occurred",
|
text: "An unexpected error occurred",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK"
|
confirmButtonText: "OK",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
|
|
@ -366,7 +452,10 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
icon: "warning",
|
icon: "warning",
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
confirmButtonText: "Yes, reset",
|
confirmButtonText: "Yes, reset",
|
||||||
cancelButtonText: "Cancel"
|
cancelButtonText: "Cancel",
|
||||||
|
customClass: {
|
||||||
|
popup: 'swal-z-index-9999'
|
||||||
|
}
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
if (mode === "single") {
|
if (mode === "single") {
|
||||||
|
|
@ -429,7 +518,7 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tabs defaultValue={mode === "single" ? "basic" : "bulk"} className="w-full">
|
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-3">
|
||||||
<TabsTrigger value="basic" disabled={isLoadingData}>Basic Information</TabsTrigger>
|
<TabsTrigger value="basic" disabled={isLoadingData}>Basic Information</TabsTrigger>
|
||||||
{/* <TabsTrigger value="hierarchy" disabled={isLoadingData}>Hierarchy</TabsTrigger> */}
|
{/* <TabsTrigger value="hierarchy" disabled={isLoadingData}>Hierarchy</TabsTrigger> */}
|
||||||
|
|
@ -590,15 +679,68 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<DynamicArray
|
<div className="space-y-4">
|
||||||
items={bulkFormData}
|
<div className="flex items-center justify-between">
|
||||||
onItemsChange={handleBulkItemsChange}
|
<h3 className="text-lg font-medium">User Levels</h3>
|
||||||
renderItem={renderBulkItemForm}
|
<Button
|
||||||
addItemLabel="Add User Level"
|
type="button"
|
||||||
emptyStateMessage="No user levels added yet. Add multiple levels to create them in bulk."
|
variant="outline"
|
||||||
allowReorder={true}
|
onClick={addBulkItem}
|
||||||
allowDuplicate={true}
|
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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
@ -636,7 +778,7 @@ export const UserLevelsForm: React.FC<UserLevelsFormProps> = ({
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<SaveIcon className="h-4 w-4" />
|
<SaveIcon className="h-4 w-4" />
|
||||||
{isSubmitting ? "Saving..." : isLoadingData ? "Loading..." : `Save ${mode === "single" ? "User Level" : "User Levels"}`}
|
{isSubmitting ? "Saving..." : isLoadingData ? "Loading..." : `Save ${activeTab === "bulk" ? "User Levels" : "User Level"}`}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2995,4 +2995,35 @@ export const RotateCcwIcon = ({ size = 24, width, height, ...props }: IconSvgPro
|
||||||
<path d="M1 4v6h6" />
|
<path d="M1 4v6h6" />
|
||||||
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10" />
|
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10" />
|
||||||
</svg>
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const EditIcon = ({
|
||||||
|
size = 24,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
...props
|
||||||
|
}: IconSvgProps) => (
|
||||||
|
<svg
|
||||||
|
width={size || width}
|
||||||
|
height={size || height}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
@ -24,6 +24,25 @@ export interface ClientApprovalSettingsRequest {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateApprovalWorkflowWithClientSettingsRequest {
|
||||||
|
workflowId: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
isActive: boolean;
|
||||||
|
isDefault: boolean;
|
||||||
|
steps: ApprovalWorkflowStepRequest[];
|
||||||
|
clientSettings: {
|
||||||
|
approvalExemptCategories: any[];
|
||||||
|
approvalExemptRoles: any[];
|
||||||
|
approvalExemptUsers: any[];
|
||||||
|
autoPublishArticles: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
requireApprovalFor: any[];
|
||||||
|
requiresApproval: boolean;
|
||||||
|
skipApprovalFor: any[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreateApprovalWorkflowWithClientSettingsRequest {
|
export interface CreateApprovalWorkflowWithClientSettingsRequest {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|
@ -208,6 +227,11 @@ export async function createApprovalWorkflowWithClientSettings(data: CreateAppro
|
||||||
return httpPostInterceptor(url, data);
|
return httpPostInterceptor(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateApprovalWorkflowWithClientSettings(data: any) {
|
||||||
|
const url = "approval-workflows/with-client-settings";
|
||||||
|
return httpPutInterceptor(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getUserLevels() {
|
export async function getUserLevels() {
|
||||||
const url = "user-levels";
|
const url = "user-levels";
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,13 @@ export async function doLogin(data: any) {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export async function getCsrfToken() {
|
export async function getCsrfToken() {
|
||||||
const pathUrl = "csrf";
|
const pathUrl = "csrf-token";
|
||||||
const headers = {
|
const headers = {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
};
|
};
|
||||||
return httpGet(pathUrl, headers);
|
return httpGet(pathUrl, headers);
|
||||||
|
|
||||||
|
|
||||||
// const url = 'https://kontenhumas.com/api/csrf';
|
// const url = 'https://kontenhumas.com/api/csrf';
|
||||||
// try {
|
// try {
|
||||||
// const response = await fetch(url, {
|
// const response = await fetch(url, {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue