fix: all errors
This commit is contained in:
parent
a3b4870092
commit
5b6ec0342b
|
|
@ -21,7 +21,7 @@ export default function AdminPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="h-full overflow-auto bg-gray-50"
|
className="h-full overflow-auto bg-gray-50 dark:bg-black"
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export default function ManagementUser() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<section className="flex flex-col gap-2 bg-white dark:bg-slate-200 rounded-lg p-3 mt-5 border">
|
<section className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3 mt-5 border">
|
||||||
<div className="flex justify-between py-3">
|
<div className="flex justify-between py-3">
|
||||||
<p className="text-lg">
|
<p className="text-lg">
|
||||||
Data User
|
Data User
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ function TenantSettingsContentTable() {
|
||||||
<div className="container mx-auto p-6 space-y-6 border rounded-lg">
|
<div className="container mx-auto p-6 space-y-6 border rounded-lg">
|
||||||
<div className="flex items-center justify-between ">
|
<div className="flex items-center justify-between ">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Tenant Settings</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Tenant Settings</h1>
|
||||||
<p className="text-gray-600 mt-2">
|
<p className="text-gray-600 mt-2">
|
||||||
Manage approval workflows and user levels for your tenant
|
Manage approval workflows and user levels for your tenant
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -255,7 +255,8 @@ function TenantSettingsContentTable() {
|
||||||
Approval Workflow Setup
|
Approval Workflow Setup
|
||||||
</h2>
|
</h2>
|
||||||
{workflow && !isEditingWorkflow && (
|
{workflow && !isEditingWorkflow && (
|
||||||
<Button variant="outline"
|
<Button
|
||||||
|
variant="outline"
|
||||||
onClick={() => setIsEditingWorkflow(true)}
|
onClick={() => setIsEditingWorkflow(true)}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
|
|
@ -265,7 +266,7 @@ function TenantSettingsContentTable() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isEditingWorkflow ? (
|
{isEditingWorkflow && workflow && workflow.workflow?.id ? (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center justify-between">
|
<CardTitle className="flex items-center justify-between">
|
||||||
|
|
@ -280,6 +281,8 @@ function TenantSettingsContentTable() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<ApprovalWorkflowForm
|
<ApprovalWorkflowForm
|
||||||
|
key={workflow.workflow.id}
|
||||||
|
workflowId={workflow.workflow.id}
|
||||||
initialData={
|
initialData={
|
||||||
workflow
|
workflow
|
||||||
? {
|
? {
|
||||||
|
|
@ -287,8 +290,12 @@ function TenantSettingsContentTable() {
|
||||||
description: workflow.workflow.description,
|
description: workflow.workflow.description,
|
||||||
isDefault: workflow.workflow.isDefault,
|
isDefault: workflow.workflow.isDefault,
|
||||||
isActive: workflow.workflow.isActive,
|
isActive: workflow.workflow.isActive,
|
||||||
requiresApproval: workflow.workflow.requiresApproval,
|
requiresApproval:
|
||||||
autoPublish: workflow.workflow.autoPublish,
|
workflow.clientSettings.requiresApproval,
|
||||||
|
// workflow.workflow.requiresApproval,
|
||||||
|
autoPublish:
|
||||||
|
workflow.clientSettings.autoPublishArticles,
|
||||||
|
// workflow.workflow.autoPublish,
|
||||||
steps:
|
steps:
|
||||||
workflow.steps?.map((step) => ({
|
workflow.steps?.map((step) => ({
|
||||||
stepOrder: step.stepOrder,
|
stepOrder: step.stepOrder,
|
||||||
|
|
@ -372,12 +379,18 @@ function TenantSettingsContentTable() {
|
||||||
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
||||||
<div
|
<div
|
||||||
className={`text-2xl font-bold ${
|
className={`text-2xl font-bold ${
|
||||||
workflow.workflow.requiresApproval
|
// workflow.workflow.requiresApproval
|
||||||
|
workflow.clientSettings.requiresApproval
|
||||||
? "text-green-600"
|
? "text-green-600"
|
||||||
: "text-red-600"
|
: "text-red-600"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{workflow.workflow.requiresApproval ? "Yes" : "No"}
|
{
|
||||||
|
// workflow.workflow.requiresApproval
|
||||||
|
workflow.clientSettings.requiresApproval
|
||||||
|
? "Yes"
|
||||||
|
: "No"
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600">
|
<div className="text-sm text-gray-600">
|
||||||
Requires Approval
|
Requires Approval
|
||||||
|
|
@ -387,12 +400,18 @@ function TenantSettingsContentTable() {
|
||||||
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
||||||
<div
|
<div
|
||||||
className={`text-2xl font-bold ${
|
className={`text-2xl font-bold ${
|
||||||
workflow.workflow.autoPublish
|
// workflow.workflow.autoPublish
|
||||||
|
workflow.clientSettings.autoPublishArticles
|
||||||
? "text-green-600"
|
? "text-green-600"
|
||||||
: "text-red-600"
|
: "text-red-600"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{workflow.workflow.autoPublish ? "Yes" : "No"}
|
{
|
||||||
|
// workflow.workflow.autoPublish
|
||||||
|
workflow.clientSettings.autoPublishArticles
|
||||||
|
? "Yes"
|
||||||
|
: "No"
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600">Auto Publish</div>
|
<div className="text-sm text-gray-600">Auto Publish</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -413,7 +432,7 @@ function TenantSettingsContentTable() {
|
||||||
{step.stepOrder}
|
{step.stepOrder}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">{step.stepName}</div>
|
<div className="font-medium dark:text-black">{step.stepName}</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
{step.conditionType &&
|
{step.conditionType &&
|
||||||
`Condition: ${step.conditionType}`}
|
`Condition: ${step.conditionType}`}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,9 @@ const AuthPage = () => {
|
||||||
const handleOTPSuccess = async () => {
|
const handleOTPSuccess = async () => {
|
||||||
if (loginCredentials) {
|
if (loginCredentials) {
|
||||||
try {
|
try {
|
||||||
await login(loginCredentials);
|
await login(loginCredentials, { skipRedirect: false });
|
||||||
|
|
||||||
|
// await login(loginCredentials);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(error.message || "Login failed after OTP verification");
|
toast.error(error.message || "Login failed after OTP verification");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
const t = useTranslations("MediaUpdate");
|
const t = useTranslations("MediaUpdate");
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [rememberMe, setRememberMe] = useState(true);
|
const [rememberMe, setRememberMe] = useState(false);
|
||||||
const [roles, setRoles] = useState<Role[]>([]);
|
const [roles, setRoles] = useState<Role[]>([]);
|
||||||
const [selectedCategory, setSelectedCategory] = useState("5");
|
const [selectedCategory, setSelectedCategory] = useState("5");
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
|
@ -62,7 +62,8 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||||
|
|
||||||
const handleLogin = async (data: LoginFormData) => {
|
const handleLogin = async (data: LoginFormData) => {
|
||||||
try {
|
try {
|
||||||
await login(data);
|
await login(data, { skipRedirect: true });
|
||||||
|
// await login(data);
|
||||||
onSuccess?.(data);
|
onSuccess?.(data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const message = getLoginErrorMessage(error);
|
const message = getLoginErrorMessage(error);
|
||||||
|
|
@ -72,7 +73,6 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||||
|
|
||||||
const onSubmit = async (data: LoginFormData) => {
|
const onSubmit = async (data: LoginFormData) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// onSuccess?.(data);
|
// onSuccess?.(data);
|
||||||
await handleLogin(data);
|
await handleLogin(data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -193,6 +193,20 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||||
{/* Remember Me and Forgot Password */}
|
{/* Remember Me and Forgot Password */}
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
|
<input
|
||||||
|
id="rememberMe"
|
||||||
|
type="checkbox"
|
||||||
|
checked={rememberMe}
|
||||||
|
onChange={(e) => setRememberMe(e.target.checked)}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||||
|
/>
|
||||||
|
<label htmlFor="rememberMe" className="text-sm cursor-pointer">
|
||||||
|
{t("rememberMe")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="flex gap-2 items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="rememberMe"
|
id="rememberMe"
|
||||||
checked={rememberMe}
|
checked={rememberMe}
|
||||||
|
|
@ -202,7 +216,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||||
<Label htmlFor="rememberMe" className="text-sm">
|
<Label htmlFor="rememberMe" className="text-sm">
|
||||||
{t("rememberMe")}
|
{t("rememberMe")}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div> */}
|
||||||
<Link
|
<Link
|
||||||
href="/auth/forgot-password"
|
href="/auth/forgot-password"
|
||||||
className="text-sm text-default-800 dark:text-default-400 leading-6 font-medium hover:underline"
|
className="text-sm text-default-800 dark:text-default-400 leading-6 font-medium hover:underline"
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export const OTPForm: React.FC<OTPFormProps> = ({
|
||||||
const [otpValue, setOtpValue] = useState("");
|
const [otpValue, setOtpValue] = useState("");
|
||||||
const t = useTranslations("MediaUpdate");
|
const t = useTranslations("MediaUpdate");
|
||||||
|
|
||||||
|
|
||||||
const handleTypeOTP = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleTypeOTP = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
const { key } = event;
|
const { key } = event;
|
||||||
const target = event.currentTarget;
|
const target = event.currentTarget;
|
||||||
|
|
@ -57,9 +58,12 @@ export const OTPForm: React.FC<OTPFormProps> = ({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isValid = await verifyOTP(loginCredentials.username, otpValue);
|
const isValid = await verifyOTP(loginCredentials.username, otpValue);
|
||||||
|
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
onError?.("Invalid OTP code");
|
onError?.("Invalid OTP code");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -154,7 +158,6 @@ export const OTPForm: React.FC<OTPFormProps> = ({
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
t("enterOTP4")
|
t("enterOTP4")
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,18 @@ interface ApprovalWorkflowFormProps {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeClientSettings = (settings: ClientApprovalSettingsRequest) => ({
|
||||||
|
approvalExemptCategories: settings.approvalExemptCategories ?? [],
|
||||||
|
approvalExemptRoles: settings.approvalExemptRoles ?? [],
|
||||||
|
approvalExemptUsers: settings.approvalExemptUsers ?? [],
|
||||||
|
requireApprovalFor: settings.requireApprovalFor ?? [],
|
||||||
|
skipApprovalFor: settings.skipApprovalFor ?? [],
|
||||||
|
|
||||||
|
autoPublishArticles: settings.autoPublishArticles ?? false,
|
||||||
|
requiresApproval: settings.requiresApproval ?? false,
|
||||||
|
isActive: settings.isActive ?? false,
|
||||||
|
});
|
||||||
|
|
||||||
export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
initialData,
|
initialData,
|
||||||
workflowId,
|
workflowId,
|
||||||
|
|
@ -41,31 +53,34 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
}) => {
|
}) => {
|
||||||
// Form state
|
// Form state
|
||||||
const [formData, setFormData] = useState<CreateApprovalWorkflowWithClientSettingsRequest>({
|
const [formData, setFormData] =
|
||||||
name: "",
|
useState<CreateApprovalWorkflowWithClientSettingsRequest>({
|
||||||
description: "",
|
name: "",
|
||||||
isActive: true,
|
description: "",
|
||||||
isDefault: true,
|
|
||||||
requiresApproval: true,
|
|
||||||
autoPublish: false,
|
|
||||||
steps: [],
|
|
||||||
clientApprovalSettings: {
|
|
||||||
requiresApproval: true,
|
|
||||||
autoPublishArticles: false,
|
|
||||||
approvalExemptUsers: [],
|
|
||||||
approvalExemptRoles: [],
|
|
||||||
approvalExemptCategories: [],
|
|
||||||
requireApprovalFor: [],
|
|
||||||
skipApprovalFor: [],
|
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
isDefault: true,
|
||||||
});
|
requiresApproval: true,
|
||||||
|
autoPublish: false,
|
||||||
|
steps: [],
|
||||||
|
clientApprovalSettings: {
|
||||||
|
requiresApproval: true,
|
||||||
|
autoPublishArticles: false,
|
||||||
|
approvalExemptUsers: [],
|
||||||
|
approvalExemptRoles: [],
|
||||||
|
approvalExemptCategories: [],
|
||||||
|
requireApprovalFor: [],
|
||||||
|
skipApprovalFor: [],
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// API data
|
// API data
|
||||||
const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
|
const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
|
||||||
const [users, setUsers] = useState<User[]>([]);
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
const [userRoles, setUserRoles] = useState<UserRole[]>([]);
|
const [userRoles, setUserRoles] = useState<UserRole[]>([]);
|
||||||
const [articleCategories, setArticleCategories] = useState<ArticleCategory[]>([]);
|
const [articleCategories, setArticleCategories] = useState<ArticleCategory[]>(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
// UI state
|
// UI state
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
|
|
@ -76,25 +91,28 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
// Get available user levels for a specific step (excluding already selected ones)
|
// Get available user levels for a specific step (excluding already selected ones)
|
||||||
const getAvailableUserLevels = (currentStepIndex: number) => {
|
const getAvailableUserLevels = (currentStepIndex: number) => {
|
||||||
const usedLevelIds = new Set<number>();
|
const usedLevelIds = new Set<number>();
|
||||||
|
|
||||||
// Collect all user level IDs that are already used by other steps
|
// Collect all user level IDs that are already used by other steps
|
||||||
formData.steps.forEach((step, stepIndex) => {
|
formData.steps.forEach((step, stepIndex) => {
|
||||||
if (stepIndex !== currentStepIndex && step.conditionValue) {
|
if (stepIndex !== currentStepIndex && step.conditionValue) {
|
||||||
try {
|
try {
|
||||||
const conditionData = JSON.parse(step.conditionValue);
|
const conditionData = JSON.parse(step.conditionValue);
|
||||||
if (conditionData.applies_to_levels && Array.isArray(conditionData.applies_to_levels)) {
|
if (
|
||||||
|
conditionData.applies_to_levels &&
|
||||||
|
Array.isArray(conditionData.applies_to_levels)
|
||||||
|
) {
|
||||||
conditionData.applies_to_levels.forEach((levelId: number) => {
|
conditionData.applies_to_levels.forEach((levelId: number) => {
|
||||||
usedLevelIds.add(levelId);
|
usedLevelIds.add(levelId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing conditionValue:', error);
|
console.error("Error parsing conditionValue:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter out used levels and return available ones
|
// Filter out used levels and return available ones
|
||||||
return userLevels.filter(level => !usedLevelIds.has(level.id));
|
return userLevels.filter((level) => !usedLevelIds.has(level.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load initial data
|
// Load initial data
|
||||||
|
|
@ -108,17 +126,20 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
const [userLevelsRes, usersRes, userRolesRes, categoriesRes] = await Promise.all([
|
const [userLevelsRes, usersRes, userRolesRes, categoriesRes] =
|
||||||
getUserLevels(),
|
await Promise.all([
|
||||||
getUsers(),
|
getUserLevels(),
|
||||||
getUserRoles(),
|
getUsers(),
|
||||||
getArticleCategories(),
|
getUserRoles(),
|
||||||
]);
|
getArticleCategories(),
|
||||||
|
]);
|
||||||
|
|
||||||
if (!userLevelsRes?.error) setUserLevels(userLevelsRes?.data?.data || []);
|
if (!userLevelsRes?.error)
|
||||||
|
setUserLevels(userLevelsRes?.data?.data || []);
|
||||||
if (!usersRes?.error) setUsers(usersRes?.data || []);
|
if (!usersRes?.error) setUsers(usersRes?.data || []);
|
||||||
if (!userRolesRes?.error) setUserRoles(userRolesRes?.data || []);
|
if (!userRolesRes?.error) setUserRoles(userRolesRes?.data || []);
|
||||||
if (!categoriesRes?.error) setArticleCategories(categoriesRes?.data || []);
|
if (!categoriesRes?.error)
|
||||||
|
setArticleCategories(categoriesRes?.data || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading form data:", error);
|
console.error("Error loading form data:", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -155,10 +176,12 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
newErrors[`steps.${index}.stepName`] = "Step name is required";
|
newErrors[`steps.${index}.stepName`] = "Step name is required";
|
||||||
}
|
}
|
||||||
if (!step.requiredUserLevelId) {
|
if (!step.requiredUserLevelId) {
|
||||||
newErrors[`steps.${index}.requiredUserLevelId`] = "Required user level is required";
|
newErrors[`steps.${index}.requiredUserLevelId`] =
|
||||||
|
"Required user level is required";
|
||||||
}
|
}
|
||||||
if (step.stepOrder <= 0) {
|
if (step.stepOrder <= 0) {
|
||||||
newErrors[`steps.${index}.stepOrder`] = "Step order must be greater than 0";
|
newErrors[`steps.${index}.stepOrder`] =
|
||||||
|
"Step order must be greater than 0";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -167,10 +190,13 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
// Form handlers
|
// Form handlers
|
||||||
const handleBasicInfoChange = (field: keyof CreateApprovalWorkflowWithClientSettingsRequest, value: any) => {
|
const handleBasicInfoChange = (
|
||||||
setFormData(prev => ({ ...prev, [field]: value }));
|
field: keyof CreateApprovalWorkflowWithClientSettingsRequest,
|
||||||
|
value: any,
|
||||||
|
) => {
|
||||||
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
if (errors[field]) {
|
if (errors[field]) {
|
||||||
setErrors(prev => ({ ...prev, [field]: "" }));
|
setErrors((prev) => ({ ...prev, [field]: "" }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -188,17 +214,26 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
// Keep existing stepOrder if manually set
|
// Keep existing stepOrder if manually set
|
||||||
return step;
|
return step;
|
||||||
});
|
});
|
||||||
|
|
||||||
setFormData(prev => ({ ...prev, steps: updatedSteps }));
|
setFormData((prev) => ({ ...prev, steps: updatedSteps }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderStepForm = (step: ApprovalWorkflowStepRequest, index: number, onUpdate: (step: ApprovalWorkflowStepRequest) => void, onDelete: () => void) => {
|
const renderStepForm = (
|
||||||
const stepErrors = Object.keys(errors).filter(key => key.startsWith(`steps.${index}`));
|
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)
|
// Check if this step has parallel steps (same stepOrder)
|
||||||
const parallelSteps = formData.steps.filter((s, i) => s.stepOrder === step.stepOrder && i !== index);
|
const parallelSteps = formData.steps.filter(
|
||||||
|
(s, i) => s.stepOrder === step.stepOrder && i !== index,
|
||||||
|
);
|
||||||
const isParallelStep = parallelSteps.length > 0;
|
const isParallelStep = parallelSteps.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Parallel Step Indicator */}
|
{/* Parallel Step Indicator */}
|
||||||
|
|
@ -222,7 +257,12 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="1"
|
placeholder="1"
|
||||||
value={step.stepOrder}
|
value={step.stepOrder}
|
||||||
onChange={(value) => onUpdate({ ...step, stepOrder: value ? Number(value) : index + 1 })}
|
onChange={(value) =>
|
||||||
|
onUpdate({
|
||||||
|
...step,
|
||||||
|
stepOrder: value ? Number(value) : index + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
error={errors[`steps.${index}.stepOrder`]}
|
error={errors[`steps.${index}.stepOrder`]}
|
||||||
min={1}
|
min={1}
|
||||||
helpText="Same order = parallel steps"
|
helpText="Same order = parallel steps"
|
||||||
|
|
@ -243,17 +283,31 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
label="Required User Level"
|
label="Required User Level"
|
||||||
name={`requiredUserLevelId-${index}`}
|
name={`requiredUserLevelId-${index}`}
|
||||||
type="select"
|
type="select"
|
||||||
placeholder={userLevels.length > 0 ? "Select user level" : "No user levels available"}
|
placeholder={
|
||||||
|
userLevels.length > 0
|
||||||
|
? "Select user level"
|
||||||
|
: "No user levels available"
|
||||||
|
}
|
||||||
value={step.requiredUserLevelId}
|
value={step.requiredUserLevelId}
|
||||||
onChange={(value) => onUpdate({ ...step, requiredUserLevelId: Number(value) })}
|
onChange={(value) =>
|
||||||
|
onUpdate({ ...step, requiredUserLevelId: Number(value) })
|
||||||
|
}
|
||||||
error={errors[`steps.${index}.requiredUserLevelId`]}
|
error={errors[`steps.${index}.requiredUserLevelId`]}
|
||||||
options={userLevels.length > 0 ? userLevels.map(level => ({
|
options={
|
||||||
value: level.id,
|
userLevels.length > 0
|
||||||
label: `${level.name} (Level ${level.levelNumber})`,
|
? userLevels.map((level) => ({
|
||||||
})) : []}
|
value: level.id,
|
||||||
|
label: `${level.name} (Level ${level.levelNumber})`,
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
}
|
||||||
required
|
required
|
||||||
disabled={userLevels.length === 0}
|
disabled={userLevels.length === 0}
|
||||||
helpText={userLevels.length === 0 ? "No user levels found. Please create user levels first." : undefined}
|
helpText={
|
||||||
|
userLevels.length === 0
|
||||||
|
? "No user levels found. Please create user levels first."
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -264,7 +318,12 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Leave empty for manual approval"
|
placeholder="Leave empty for manual approval"
|
||||||
value={step.autoApproveAfterHours}
|
value={step.autoApproveAfterHours}
|
||||||
onChange={(value) => onUpdate({ ...step, autoApproveAfterHours: value ? Number(value) : undefined })}
|
onChange={(value) =>
|
||||||
|
onUpdate({
|
||||||
|
...step,
|
||||||
|
autoApproveAfterHours: value ? Number(value) : undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
helpText="Automatically approve after specified hours"
|
helpText="Automatically approve after specified hours"
|
||||||
min={1}
|
min={1}
|
||||||
/>
|
/>
|
||||||
|
|
@ -277,7 +336,7 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
value={step.canSkip || false}
|
value={step.canSkip || false}
|
||||||
onChange={(value) => onUpdate({ ...step, canSkip: value })}
|
onChange={(value) => onUpdate({ ...step, canSkip: value })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label="Is Active"
|
label="Is Active"
|
||||||
name={`isActive-${index}`}
|
name={`isActive-${index}`}
|
||||||
|
|
@ -294,32 +353,42 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
label="Applies to User Levels"
|
label="Applies to User Levels"
|
||||||
placeholder={(() => {
|
placeholder={(() => {
|
||||||
const availableLevels = getAvailableUserLevels(index);
|
const availableLevels = getAvailableUserLevels(index);
|
||||||
return availableLevels.length > 0 ? "Select user levels..." : "No available user levels";
|
return availableLevels.length > 0
|
||||||
|
? "Select user levels..."
|
||||||
|
: "No available user levels";
|
||||||
})()}
|
})()}
|
||||||
options={(() => {
|
options={(() => {
|
||||||
const availableLevels = getAvailableUserLevels(index);
|
const availableLevels = getAvailableUserLevels(index);
|
||||||
return availableLevels.map(level => ({
|
return availableLevels.map((level) => ({
|
||||||
value: level.id,
|
value: level.id,
|
||||||
label: `${level.name} (Level ${level.levelNumber})`,
|
label: `${level.name} (Level ${level.levelNumber})`,
|
||||||
}));
|
}));
|
||||||
})()}
|
})()}
|
||||||
value={(() => {
|
value={(() => {
|
||||||
try {
|
try {
|
||||||
return step.conditionValue ? JSON.parse(step.conditionValue).applies_to_levels || [] : [];
|
return step.conditionValue
|
||||||
|
? JSON.parse(step.conditionValue).applies_to_levels || []
|
||||||
|
: [];
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
const conditionValue = JSON.stringify({ applies_to_levels: value });
|
const conditionValue = JSON.stringify({
|
||||||
onUpdate({ ...step, conditionType: "user_level_hierarchy", conditionValue });
|
applies_to_levels: value,
|
||||||
|
});
|
||||||
|
onUpdate({
|
||||||
|
...step,
|
||||||
|
conditionType: "user_level_hierarchy",
|
||||||
|
conditionValue,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
searchable={true}
|
searchable={true}
|
||||||
disabled={getAvailableUserLevels(index).length === 0}
|
disabled={getAvailableUserLevels(index).length === 0}
|
||||||
helpText={(() => {
|
helpText={(() => {
|
||||||
const availableLevels = getAvailableUserLevels(index);
|
const availableLevels = getAvailableUserLevels(index);
|
||||||
const usedLevels = userLevels.length - availableLevels.length;
|
const usedLevels = userLevels.length - availableLevels.length;
|
||||||
|
|
||||||
if (availableLevels.length === 0) {
|
if (availableLevels.length === 0) {
|
||||||
return "All user levels are already assigned to other steps";
|
return "All user levels are already assigned to other steps";
|
||||||
} else if (usedLevels > 0) {
|
} else if (usedLevels > 0) {
|
||||||
|
|
@ -334,9 +403,13 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function normalizeBoolean(value?: boolean): boolean {
|
||||||
|
return value ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!validateForm()) {
|
if (!validateForm()) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Validation Error",
|
title: "Validation Error",
|
||||||
|
|
@ -344,14 +417,14 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
customClass: {
|
customClass: {
|
||||||
popup: 'swal-z-index-9999'
|
popup: "swal-z-index-9999",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Hardcoded client approval settings
|
// Hardcoded client approval settings
|
||||||
const hardcodedClientSettings = {
|
const hardcodedClientSettings = {
|
||||||
|
|
@ -362,7 +435,23 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
isActive: true,
|
isActive: true,
|
||||||
requireApprovalFor: [],
|
requireApprovalFor: [],
|
||||||
requiresApproval: true,
|
requiresApproval: true,
|
||||||
skipApprovalFor: []
|
skipApprovalFor: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const clientSettingsPayload = {
|
||||||
|
approvalExemptCategories: [],
|
||||||
|
approvalExemptRoles: [],
|
||||||
|
approvalExemptUsers: [],
|
||||||
|
requireApprovalFor: [],
|
||||||
|
skipApprovalFor: [],
|
||||||
|
|
||||||
|
autoPublishArticles:
|
||||||
|
formData.clientApprovalSettings.autoPublishArticles ?? false,
|
||||||
|
|
||||||
|
requiresApproval:
|
||||||
|
formData.clientApprovalSettings.requiresApproval ?? false,
|
||||||
|
|
||||||
|
isActive: formData.clientApprovalSettings.isActive ?? false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (workflowId) {
|
if (workflowId) {
|
||||||
|
|
@ -371,30 +460,60 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
workflowId,
|
workflowId,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
isActive: formData.isActive || false,
|
|
||||||
isDefault: formData.isDefault || false,
|
isActive: normalizeBoolean(formData.isActive),
|
||||||
steps: formData.steps.map(step => ({
|
isDefault: normalizeBoolean(formData.isDefault),
|
||||||
|
|
||||||
|
steps: formData.steps.map((step) => ({
|
||||||
...step,
|
...step,
|
||||||
branchName: step.stepName, // branchName should be same as stepName
|
branchName: step.stepName,
|
||||||
})),
|
})),
|
||||||
clientSettings: hardcodedClientSettings
|
|
||||||
|
clientSettings: {
|
||||||
|
approvalExemptCategories: [],
|
||||||
|
approvalExemptRoles: [],
|
||||||
|
approvalExemptUsers: [],
|
||||||
|
requireApprovalFor: [],
|
||||||
|
skipApprovalFor: [],
|
||||||
|
|
||||||
|
// 🔥 AMBIL LANGSUNG DARI CHECKBOX UI
|
||||||
|
requiresApproval: normalizeBoolean(formData.requiresApproval),
|
||||||
|
autoPublishArticles: normalizeBoolean(formData.autoPublish),
|
||||||
|
isActive: normalizeBoolean(formData.isActive),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const updateData: UpdateApprovalWorkflowWithClientSettingsRequest = {
|
||||||
|
// workflowId,
|
||||||
|
// name: formData.name,
|
||||||
|
// description: formData.description,
|
||||||
|
// isActive: formData.isActive,
|
||||||
|
// isDefault: formData.isDefault,
|
||||||
|
// steps: formData.steps.map(step => ({
|
||||||
|
// ...step,
|
||||||
|
// branchName: step.stepName, // branchName should be same as stepName
|
||||||
|
// })),
|
||||||
|
// clientSettings: hardcodedClientSettings
|
||||||
|
// };
|
||||||
|
|
||||||
console.log("Update Data: ", updateData);
|
console.log("Update Data: ", updateData);
|
||||||
|
|
||||||
const response = await updateApprovalWorkflowWithClientSettings(updateData);
|
const response =
|
||||||
|
await updateApprovalWorkflowWithClientSettings(updateData);
|
||||||
|
|
||||||
console.log("Update 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 update approval workflow",
|
text:
|
||||||
|
response?.message?.messages?.[0] ||
|
||||||
|
"Failed to update approval workflow",
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
customClass: {
|
customClass: {
|
||||||
popup: 'swal-z-index-9999'
|
popup: "swal-z-index-9999",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
|
|
@ -403,8 +522,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
icon: "success",
|
icon: "success",
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
customClass: {
|
customClass: {
|
||||||
popup: 'swal-z-index-9999'
|
popup: "swal-z-index-9999",
|
||||||
}
|
},
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Call onSave to trigger parent refresh
|
// Call onSave to trigger parent refresh
|
||||||
if (onSave) {
|
if (onSave) {
|
||||||
|
|
@ -416,24 +535,27 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
// Create mode
|
// Create mode
|
||||||
const submitData = {
|
const submitData = {
|
||||||
...formData,
|
...formData,
|
||||||
clientApprovalSettings: hardcodedClientSettings
|
clientApprovalSettings: hardcodedClientSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Create Data: ", submitData);
|
console.log("Create Data: ", submitData);
|
||||||
|
|
||||||
const response = await createApprovalWorkflowWithClientSettings(submitData);
|
const response =
|
||||||
|
await createApprovalWorkflowWithClientSettings(submitData);
|
||||||
|
|
||||||
console.log("Create 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: {
|
customClass: {
|
||||||
popup: 'swal-z-index-9999'
|
popup: "swal-z-index-9999",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
|
|
@ -442,8 +564,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
icon: "success",
|
icon: "success",
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
customClass: {
|
customClass: {
|
||||||
popup: 'swal-z-index-9999'
|
popup: "swal-z-index-9999",
|
||||||
}
|
},
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Call onSave to trigger parent refresh
|
// Call onSave to trigger parent refresh
|
||||||
if (onSave) {
|
if (onSave) {
|
||||||
|
|
@ -460,8 +582,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
icon: "error",
|
icon: "error",
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
customClass: {
|
customClass: {
|
||||||
popup: 'swal-z-index-9999'
|
popup: "swal-z-index-9999",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
|
|
@ -477,8 +599,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
confirmButtonText: "Yes, reset",
|
confirmButtonText: "Yes, reset",
|
||||||
cancelButtonText: "Cancel",
|
cancelButtonText: "Cancel",
|
||||||
customClass: {
|
customClass: {
|
||||||
popup: 'swal-z-index-9999'
|
popup: "swal-z-index-9999",
|
||||||
}
|
},
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
setFormData({
|
setFormData({
|
||||||
|
|
@ -513,12 +635,18 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
<span className="ml-2 text-gray-600">Loading form data...</span>
|
<span className="ml-2 text-gray-600">Loading form data...</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tabs defaultValue="basic" className="w-full">
|
<Tabs defaultValue="basic" 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}>
|
||||||
<TabsTrigger value="steps" disabled={isLoadingData}>Workflow Steps</TabsTrigger>
|
Basic Information
|
||||||
<TabsTrigger value="settings" disabled={isLoadingData}>Client Settings</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="steps" disabled={isLoadingData}>
|
||||||
|
Workflow Steps
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="settings" disabled={isLoadingData}>
|
||||||
|
Client Settings
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* Basic Information Tab */}
|
{/* Basic Information Tab */}
|
||||||
|
|
@ -545,7 +673,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
type="textarea"
|
type="textarea"
|
||||||
placeholder="Describe the purpose and process of this workflow"
|
placeholder="Describe the purpose and process of this workflow"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(value) => handleBasicInfoChange("description", value)}
|
onChange={(value) =>
|
||||||
|
handleBasicInfoChange("description", value)
|
||||||
|
}
|
||||||
error={errors.description}
|
error={errors.description}
|
||||||
required
|
required
|
||||||
rows={4}
|
rows={4}
|
||||||
|
|
@ -565,7 +695,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
name="isDefault"
|
name="isDefault"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value={formData.isDefault}
|
value={formData.isDefault}
|
||||||
onChange={(value) => handleBasicInfoChange("isDefault", value)}
|
onChange={(value) =>
|
||||||
|
handleBasicInfoChange("isDefault", value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
|
|
@ -573,7 +705,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
name="requiresApproval"
|
name="requiresApproval"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value={formData.requiresApproval}
|
value={formData.requiresApproval}
|
||||||
onChange={(value) => handleBasicInfoChange("requiresApproval", value)}
|
onChange={(value) =>
|
||||||
|
handleBasicInfoChange("requiresApproval", value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
|
|
@ -581,7 +715,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
name="autoPublish"
|
name="autoPublish"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value={formData.autoPublish}
|
value={formData.autoPublish}
|
||||||
onChange={(value) => handleBasicInfoChange("autoPublish", value)}
|
onChange={(value) =>
|
||||||
|
handleBasicInfoChange("autoPublish", value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -595,14 +731,22 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
<CardTitle className="flex items-center justify-between">
|
<CardTitle className="flex items-center justify-between">
|
||||||
<span>Workflow Steps Configuration</span>
|
<span>Workflow Steps Configuration</span>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
{formData.steps.length} step{formData.steps.length !== 1 ? 's' : ''}
|
{formData.steps.length} step
|
||||||
|
{formData.steps.length !== 1 ? "s" : ""}
|
||||||
{(() => {
|
{(() => {
|
||||||
const parallelGroups = formData.steps.reduce((acc, step) => {
|
const parallelGroups = formData.steps.reduce(
|
||||||
acc[step.stepOrder] = (acc[step.stepOrder] || 0) + 1;
|
(acc, step) => {
|
||||||
return acc;
|
acc[step.stepOrder] = (acc[step.stepOrder] || 0) + 1;
|
||||||
}, {} as Record<number, number>);
|
return acc;
|
||||||
const parallelCount = Object.values(parallelGroups).filter(count => count > 1).length;
|
},
|
||||||
return parallelCount > 0 ? ` • ${parallelCount} parallel group${parallelCount !== 1 ? 's' : ''}` : '';
|
{} as Record<number, number>,
|
||||||
|
);
|
||||||
|
const parallelCount = Object.values(parallelGroups).filter(
|
||||||
|
(count) => count > 1,
|
||||||
|
).length;
|
||||||
|
return parallelCount > 0
|
||||||
|
? ` • ${parallelCount} parallel group${parallelCount !== 1 ? "s" : ""}`
|
||||||
|
: "";
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
|
|
@ -636,21 +780,44 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
<div className="h-12 w-12 mx-auto mb-2 bg-gray-100 rounded-full flex items-center justify-center">
|
<div className="h-12 w-12 mx-auto mb-2 bg-gray-100 rounded-full flex items-center justify-center">
|
||||||
<span className="text-gray-400 text-xl">⚙️</span>
|
<span className="text-gray-400 text-xl">⚙️</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Settings Pre-configured</h3>
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||||
|
Settings Pre-configured
|
||||||
|
</h3>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
Client approval settings are automatically configured with optimal defaults.
|
Client approval settings are automatically configured with
|
||||||
|
optimal defaults.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 rounded-lg p-4 text-left max-w-md mx-auto">
|
<div className="bg-gray-50 rounded-lg p-4 text-left max-w-md mx-auto">
|
||||||
<h4 className="font-medium text-gray-900 mb-2">Default Settings:</h4>
|
<h4 className="font-medium text-gray-900 mb-2">
|
||||||
|
Default Settings:
|
||||||
|
</h4>
|
||||||
<ul className="text-sm text-gray-600 space-y-1">
|
<ul className="text-sm text-gray-600 space-y-1">
|
||||||
<li>• Requires Approval: <span className="font-medium text-green-600">Yes</span></li>
|
<li>
|
||||||
<li>• Auto Publish Articles: <span className="font-medium text-green-600">Yes</span></li>
|
• Requires Approval:{" "}
|
||||||
<li>• Is Active: <span className="font-medium text-green-600">Yes</span></li>
|
<span className="font-medium text-green-600">Yes</span>
|
||||||
<li>• Exempt Users: <span className="font-medium text-gray-500">None</span></li>
|
</li>
|
||||||
<li>• Exempt Roles: <span className="font-medium text-gray-500">None</span></li>
|
<li>
|
||||||
<li>• Exempt Categories: <span className="font-medium text-gray-500">None</span></li>
|
• Auto Publish Articles:{" "}
|
||||||
|
<span className="font-medium text-green-600">Yes</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
• Is Active:{" "}
|
||||||
|
<span className="font-medium text-green-600">Yes</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
• Exempt Users:{" "}
|
||||||
|
<span className="font-medium text-gray-500">None</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
• Exempt Roles:{" "}
|
||||||
|
<span className="font-medium text-gray-500">None</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
• Exempt Categories:{" "}
|
||||||
|
<span className="font-medium text-gray-500">None</span>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -684,7 +851,7 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
@ -692,7 +859,13 @@ 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..." : workflowId ? "Update Workflow" : "Save Workflow"}
|
{isSubmitting
|
||||||
|
? "Saving..."
|
||||||
|
: isLoadingData
|
||||||
|
? "Loading..."
|
||||||
|
: workflowId
|
||||||
|
? "Update Workflow"
|
||||||
|
: "Save Workflow"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const Loader = () => {
|
||||||
<div className="flex gap-2 items-center ">
|
<div className="flex gap-2 items-center ">
|
||||||
{/* <DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background" /> */}
|
{/* <DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background" /> */}
|
||||||
<Image
|
<Image
|
||||||
src="/assets/mediahub-logo-min.png"
|
src="/assets/logo1.png"
|
||||||
alt=""
|
alt=""
|
||||||
width={80}
|
width={80}
|
||||||
height={80}
|
height={80}
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ const UserInternalTable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white dark:bg-slate-200">
|
<div className="bg-white dark:bg-black">
|
||||||
<div className="flex justify-between py-3">
|
<div className="flex justify-between py-3">
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
||||||
|
|
@ -64,18 +64,21 @@ export const useAuth = (): AuthContextType => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const login = useCallback(
|
const login = useCallback(
|
||||||
async (credentials: LoginFormData): Promise<void> => {
|
async (
|
||||||
|
credentials: LoginFormData,
|
||||||
|
options?: { skipRedirect?: boolean },
|
||||||
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
setState((prev) => ({ ...prev, loading: true, error: null }));
|
setState((prev) => ({ ...prev, loading: true, error: null }));
|
||||||
|
|
||||||
// Check rate limiting
|
// Check rate limiting
|
||||||
if (!loginRateLimiter.canAttempt(credentials.username)) {
|
if (!loginRateLimiter.canAttempt(credentials.username)) {
|
||||||
const remainingTime = loginRateLimiter.getRemainingTime(
|
const remainingTime = loginRateLimiter.getRemainingTime(
|
||||||
credentials.username
|
credentials.username,
|
||||||
);
|
);
|
||||||
const minutes = Math.ceil(remainingTime / (60 * 1000));
|
const minutes = Math.ceil(remainingTime / (60 * 1000));
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Too many login attempts. Please try again in ${minutes} minutes.`
|
`Too many login attempts. Please try again in ${minutes} minutes.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,10 +122,14 @@ export const useAuth = (): AuthContextType => {
|
||||||
Cookies.set("time_refresh", newTime, {
|
Cookies.set("time_refresh", newTime, {
|
||||||
expires: 1,
|
expires: 1,
|
||||||
});
|
});
|
||||||
if (response?.data?.data?.approvalWorkflowInfo?.hasWorkflowSetup) {
|
if (response?.data?.data?.approvalWorkflowInfo?.hasWorkflowSetup) {
|
||||||
Cookies.set("default_workflow", response?.data?.data?.approvalWorkflowInfo?.defaultWorkflowId, {
|
Cookies.set(
|
||||||
expires: 1,
|
"default_workflow",
|
||||||
});
|
response?.data?.data?.approvalWorkflowInfo?.defaultWorkflowId,
|
||||||
|
{
|
||||||
|
expires: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cookies.set("is_first_login", "true", {
|
Cookies.set("is_first_login", "true", {
|
||||||
|
|
@ -176,12 +183,20 @@ export const useAuth = (): AuthContextType => {
|
||||||
|
|
||||||
// Reset rate limiter on successful login
|
// Reset rate limiter on successful login
|
||||||
loginRateLimiter.resetAttempts(credentials.username);
|
loginRateLimiter.resetAttempts(credentials.username);
|
||||||
|
|
||||||
if (profile?.userRoleId === 4 || profile?.userRoleId === 5) {
|
if (!options?.skipRedirect) {
|
||||||
router.push("/");
|
if (profile?.userRoleId === 4 || profile?.userRoleId === 5) {
|
||||||
} else {
|
router.push("/");
|
||||||
router.push("/admin/dashboard");
|
} else {
|
||||||
|
router.push("/admin/dashboard");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (profile?.userRoleId === 4 || profile?.userRoleId === 5) {
|
||||||
|
// router.push("/");
|
||||||
|
// } else {
|
||||||
|
// router.push("/admin/dashboard");
|
||||||
|
// }
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMessage = error?.message || "Login failed";
|
const errorMessage = error?.message || "Login failed";
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
|
|
@ -192,7 +207,7 @@ export const useAuth = (): AuthContextType => {
|
||||||
showAuthError(error, "Login failed");
|
showAuthError(error, "Login failed");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[router]
|
[router],
|
||||||
);
|
);
|
||||||
|
|
||||||
const logout = useCallback((): void => {
|
const logout = useCallback((): void => {
|
||||||
|
|
@ -266,7 +281,7 @@ export const useEmailValidation = () => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -284,7 +299,7 @@ export const useEmailSetup = () => {
|
||||||
const setupEmail = useCallback(
|
const setupEmail = useCallback(
|
||||||
async (
|
async (
|
||||||
credentials: LoginFormData,
|
credentials: LoginFormData,
|
||||||
emailData: EmailValidationData
|
emailData: EmailValidationData,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -322,7 +337,7 @@ export const useEmailSetup = () => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -349,7 +364,7 @@ export const useOTPVerification = () => {
|
||||||
const data = {
|
const data = {
|
||||||
username: username,
|
username: username,
|
||||||
otpCode: otp,
|
otpCode: otp,
|
||||||
}
|
};
|
||||||
const response = await verifyOTPByUsername(data);
|
const response = await verifyOTPByUsername(data);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
|
|
@ -366,7 +381,7 @@ export const useOTPVerification = () => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export interface ProfileData {
|
||||||
fullname: string;
|
fullname: string;
|
||||||
email: string;
|
email: string;
|
||||||
roleId: number;
|
roleId: number;
|
||||||
userRoleId:number;
|
userRoleId: number;
|
||||||
role: {
|
role: {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
@ -131,7 +131,10 @@ export interface AuthState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthContextType extends AuthState {
|
export interface AuthContextType extends AuthState {
|
||||||
login: (credentials: LoginFormData) => Promise<void>;
|
login: (
|
||||||
|
credentials: LoginFormData,
|
||||||
|
options?: { skipRedirect?: boolean },
|
||||||
|
) => Promise<void>;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
refreshToken: () => Promise<void>;
|
refreshToken: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
@ -171,4 +174,4 @@ export interface AuthCookies {
|
||||||
uinse: string; // user institute id encrypted
|
uinse: string; // user institute id encrypted
|
||||||
status: string;
|
status: string;
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue