feat: update tenant - user level

This commit is contained in:
hanif salafi 2025-10-03 07:53:19 +07:00
parent f7475d16fd
commit 957d3c4e98
7 changed files with 413 additions and 60 deletions

View File

@ -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>

View File

@ -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 {

View File

@ -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,39 +365,103 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
skipApprovalFor: [] skipApprovalFor: []
}; };
if (workflowId) {
// Update mode
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("Update Data: ", updateData);
const response = await updateApprovalWorkflowWithClientSettings(updateData);
console.log("Update Response: ", response);
if (response?.error) {
Swal.fire({
title: "Error",
text: response?.message?.messages?.[0] || "Failed to update approval workflow",
icon: "error",
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 {
// Create mode
const submitData = { const submitData = {
...formData, ...formData,
clientApprovalSettings: hardcodedClientSettings clientApprovalSettings: hardcodedClientSettings
}; };
console.log("Submit Data: ", submitData); console.log("Create Data: ", submitData);
const response = await createApprovalWorkflowWithClientSettings(submitData); const response = await createApprovalWorkflowWithClientSettings(submitData);
console.log("Response: ", response); console.log("Create 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 create approval workflow",
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: "Approval workflow created successfully", text: "Approval workflow created successfully",
icon: "success", icon: "success",
confirmButtonText: "OK" 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);
Swal.fire({ Swal.fire({
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>

View File

@ -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>

View File

@ -2996,3 +2996,34 @@ export const RotateCcwIcon = ({ size = 24, width, height, ...props }: IconSvgPro
<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>
);

View File

@ -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);

View File

@ -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, {