"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 { SaveIcon, EyeIcon, RotateCcwIcon } from "@/components/icons"; import { FormField } from "./common/FormField"; import { DynamicArray } from "./common/DynamicArray"; import { MultiSelect } from "./common/MultiSelect"; import { CreateApprovalWorkflowWithClientSettingsRequest, UpdateApprovalWorkflowWithClientSettingsRequest, ApprovalWorkflowStepRequest, ClientApprovalSettingsRequest, UserLevel, User, UserRole, ArticleCategory, createApprovalWorkflowWithClientSettings, updateApprovalWorkflowWithClientSettings, getUserLevels, getUsers, getUserRoles, getArticleCategories, } from "@/service/approval-workflows"; import Swal from "sweetalert2"; interface ApprovalWorkflowFormProps { initialData?: CreateApprovalWorkflowWithClientSettingsRequest; workflowId?: number; // For update mode onSave?: (data: CreateApprovalWorkflowWithClientSettingsRequest) => void; onCancel?: () => void; isLoading?: boolean; } export const ApprovalWorkflowForm: React.FC = ({ initialData, workflowId, onSave, onCancel, isLoading = false, }) => { // Form state const [formData, setFormData] = useState({ name: "", description: "", isActive: true, isDefault: true, requiresApproval: true, autoPublish: false, steps: [], clientApprovalSettings: { requiresApproval: true, autoPublishArticles: false, approvalExemptUsers: [], approvalExemptRoles: [], approvalExemptCategories: [], requireApprovalFor: [], skipApprovalFor: [], isActive: true, }, }); // API data const [userLevels, setUserLevels] = useState([]); const [users, setUsers] = useState([]); const [userRoles, setUserRoles] = useState([]); const [articleCategories, setArticleCategories] = useState([]); // UI state const [errors, setErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); const [expandedSteps, setExpandedSteps] = useState>(new Set()); const [isLoadingData, setIsLoadingData] = useState(true); // Get available user levels for a specific step (excluding already selected ones) const getAvailableUserLevels = (currentStepIndex: number) => { const usedLevelIds = new Set(); // Collect all user level IDs that are already used by other steps formData.steps.forEach((step, stepIndex) => { if (stepIndex !== currentStepIndex && step.conditionValue) { try { const conditionData = JSON.parse(step.conditionValue); if (conditionData.applies_to_levels && Array.isArray(conditionData.applies_to_levels)) { conditionData.applies_to_levels.forEach((levelId: number) => { usedLevelIds.add(levelId); }); } } catch (error) { console.error('Error parsing conditionValue:', error); } } }); // Filter out used levels and return available ones return userLevels.filter(level => !usedLevelIds.has(level.id)); }; // Load initial data useEffect(() => { if (initialData) { setFormData(initialData); } }, [initialData]); // Load API data useEffect(() => { const loadData = async () => { try { const [userLevelsRes, usersRes, userRolesRes, categoriesRes] = await Promise.all([ getUserLevels(), getUsers(), getUserRoles(), getArticleCategories(), ]); if (!userLevelsRes?.error) setUserLevels(userLevelsRes?.data?.data || []); if (!usersRes?.error) setUsers(usersRes?.data || []); if (!userRolesRes?.error) setUserRoles(userRolesRes?.data || []); if (!categoriesRes?.error) setArticleCategories(categoriesRes?.data || []); } catch (error) { console.error("Error loading form data:", error); } finally { setIsLoadingData(false); } }; loadData(); }, []); // Validation const validateForm = (): boolean => { const newErrors: Record = {}; if (!formData.name.trim()) { newErrors.name = "Workflow name is required"; } else if (formData.name.trim().length < 3) { newErrors.name = "Workflow name must be at least 3 characters"; } if (!formData.description.trim()) { newErrors.description = "Description is required"; } else if (formData.description.trim().length < 3) { newErrors.description = "Description must be at least 3 characters"; } if (formData.steps.length === 0) { newErrors.steps = "At least one workflow step is required"; } // Validate steps formData.steps.forEach((step, index) => { if (!step.stepName.trim()) { newErrors[`steps.${index}.stepName`] = "Step name is required"; } if (!step.requiredUserLevelId) { newErrors[`steps.${index}.requiredUserLevelId`] = "Required user level is required"; } if (step.stepOrder <= 0) { newErrors[`steps.${index}.stepOrder`] = "Step order must be greater than 0"; } }); setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // Form handlers const handleBasicInfoChange = (field: keyof CreateApprovalWorkflowWithClientSettingsRequest, value: any) => { setFormData(prev => ({ ...prev, [field]: value })); if (errors[field]) { setErrors(prev => ({ ...prev, [field]: "" })); } }; const handleStepsChange = (steps: ApprovalWorkflowStepRequest[]) => { // Allow manual step order assignment for parallel steps // Don't auto-assign if user has manually set stepOrder const updatedSteps = steps.map((step, index) => { // If stepOrder is not set or is 0, auto-assign based on index if (!step.stepOrder || step.stepOrder === 0) { return { ...step, stepOrder: index + 1, }; } // Keep existing stepOrder if manually set return step; }); setFormData(prev => ({ ...prev, steps: updatedSteps })); }; const renderStepForm = (step: ApprovalWorkflowStepRequest, index: number, onUpdate: (step: ApprovalWorkflowStepRequest) => void, onDelete: () => void) => { const stepErrors = Object.keys(errors).filter(key => key.startsWith(`steps.${index}`)); // Check if this step has parallel steps (same stepOrder) const parallelSteps = formData.steps.filter((s, i) => s.stepOrder === step.stepOrder && i !== index); const isParallelStep = parallelSteps.length > 0; return (
{/* Parallel Step Indicator */} {isParallelStep && (
Parallel Step - Order {step.stepOrder} ({parallelSteps.length + 1} steps running simultaneously)
)}
onUpdate({ ...step, stepOrder: value ? Number(value) : index + 1 })} error={errors[`steps.${index}.stepOrder`]} min={1} helpText="Same order = parallel steps" /> onUpdate({ ...step, stepName: value })} error={errors[`steps.${index}.stepName`]} required /> 0 ? "Select user level" : "No user levels available"} value={step.requiredUserLevelId} onChange={(value) => onUpdate({ ...step, requiredUserLevelId: Number(value) })} error={errors[`steps.${index}.requiredUserLevelId`]} options={userLevels.length > 0 ? userLevels.map(level => ({ value: level.id, label: `${level.name} (Level ${level.levelNumber})`, })) : []} required disabled={userLevels.length === 0} helpText={userLevels.length === 0 ? "No user levels found. Please create user levels first." : undefined} />
onUpdate({ ...step, autoApproveAfterHours: value ? Number(value) : undefined })} helpText="Automatically approve after specified hours" min={1} />
onUpdate({ ...step, canSkip: value })} /> onUpdate({ ...step, isActive: value })} />
{/* Condition Settings */}
{ const availableLevels = getAvailableUserLevels(index); return availableLevels.length > 0 ? "Select user levels..." : "No available user levels"; })()} options={(() => { const availableLevels = getAvailableUserLevels(index); return availableLevels.map(level => ({ value: level.id, label: `${level.name} (Level ${level.levelNumber})`, })); })()} value={(() => { try { return step.conditionValue ? JSON.parse(step.conditionValue).applies_to_levels || [] : []; } catch { return []; } })()} onChange={(value) => { const conditionValue = JSON.stringify({ applies_to_levels: value }); onUpdate({ ...step, conditionType: "user_level_hierarchy", conditionValue }); }} searchable={true} disabled={getAvailableUserLevels(index).length === 0} helpText={(() => { const availableLevels = getAvailableUserLevels(index); const usedLevels = userLevels.length - availableLevels.length; if (availableLevels.length === 0) { return "All user levels are already assigned to other steps"; } else if (usedLevels > 0) { return `${usedLevels} user level(s) already assigned to other steps. ${availableLevels.length} available.`; } else { return "Select which user levels this step applies to"; } })()} />
); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) { 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 { // Hardcoded client approval settings const hardcodedClientSettings = { approvalExemptCategories: [], approvalExemptRoles: [], approvalExemptUsers: [], autoPublishArticles: true, isActive: true, requireApprovalFor: [], requiresApproval: true, 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 = { ...formData, clientApprovalSettings: hardcodedClientSettings }; 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) { 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) { setFormData({ name: "", description: "", isActive: true, isDefault: false, requiresApproval: true, autoPublish: false, steps: [], clientApprovalSettings: { requiresApproval: true, autoPublishArticles: false, approvalExemptUsers: [], approvalExemptRoles: [], approvalExemptCategories: [], requireApprovalFor: [], skipApprovalFor: [], isActive: true, }, }); setErrors({}); } }); }; return (
{isLoadingData && (
Loading form data...
)} Basic Information Workflow Steps Client Settings {/* Basic Information Tab */} Workflow Basic Information handleBasicInfoChange("name", value)} error={errors.name} required /> handleBasicInfoChange("description", value)} error={errors.description} required rows={4} />
handleBasicInfoChange("isActive", value)} /> handleBasicInfoChange("isDefault", value)} /> handleBasicInfoChange("requiresApproval", value)} /> handleBasicInfoChange("autoPublish", value)} />
{/* Workflow Steps Tab */} Workflow Steps Configuration
{formData.steps.length} step{formData.steps.length !== 1 ? 's' : ''} {(() => { const parallelGroups = formData.steps.reduce((acc, step) => { acc[step.stepOrder] = (acc[step.stepOrder] || 0) + 1; return acc; }, {} as Record); const parallelCount = Object.values(parallelGroups).filter(count => count > 1).length; return parallelCount > 0 ? ` • ${parallelCount} parallel group${parallelCount !== 1 ? 's' : ''}` : ''; })()}
{errors.steps && (

{errors.steps}

)}
{/* Client Settings Tab */} Client Approval Settings
⚙️

Settings Pre-configured

Client approval settings are automatically configured with optimal defaults.

Default Settings:

  • • Requires Approval: Yes
  • • Auto Publish Articles: Yes
  • • Is Active: Yes
  • • Exempt Users: None
  • • Exempt Roles: None
  • • Exempt Categories: None
{/* Form Actions */}
{onCancel && ( )}
); };