diff --git a/src/core/components/base/form-field.tsx b/src/core/components/base/form-field.tsx index 70f58ac..6da25bd 100644 --- a/src/core/components/base/form-field.tsx +++ b/src/core/components/base/form-field.tsx @@ -63,10 +63,10 @@ const FormField: FC = ({ field.Type === FieldTypes.پول) && typeof currentValue === "number" ) { - if (field.MinValue !== undefined && currentValue < field?.MinValue) { + if (field.MinValue !== undefined && currentValue < field?.MinValue!) { errorMessage = `مقدار باید بزرگتر یا مساوی ${field.MinValue} باشد.`; } - if (field.MaxValue !== undefined && currentValue > field?.MaxValue) { + if (field.MaxValue !== undefined && currentValue > field?.MaxValue!) { errorMessage = `مقدار باید کوچکتر یا مساوی ${field.MaxValue} باشد.`; } } @@ -130,7 +130,7 @@ const FormField: FC = ({ const handleChange = ( e: ChangeEvent ) => { - const newValue = normalizeInput(field.Type, e.target.value); + const newValue = normalizeInput(field?.Type, e.target.value); onChange(newValue); }; @@ -163,7 +163,7 @@ const FormField: FC = ({ ); @@ -181,7 +181,7 @@ const FormField: FC = ({ return ( ); @@ -361,7 +361,7 @@ const FormField: FC = ({ ); diff --git a/src/core/service/api-address.ts b/src/core/service/api-address.ts index c119ab1..4387a1d 100644 --- a/src/core/service/api-address.ts +++ b/src/core/service/api-address.ts @@ -9,6 +9,7 @@ export const API_ADDRESS = { delete: "/api/delete", uploadImage: "/workflow/uploadImage", index: "workflow/index", + index2: "workflow/index2", // LOGIN: "/auth/login", // VERIFY_OTP: "/auth/verify-otp", // REGISTER: "/auth/register", diff --git a/src/core/utils/dynamic-field.utils.ts b/src/core/utils/dynamic-field.utils.ts index 0f8a38e..1759e10 100644 --- a/src/core/utils/dynamic-field.utils.ts +++ b/src/core/utils/dynamic-field.utils.ts @@ -49,6 +49,7 @@ export interface FieldDefinition { Description_HasSaving: boolean; Description_IsParser: boolean; Description_Text: string; + Description: string; Document: string; @@ -79,6 +80,7 @@ export interface FieldDefinition { Option_IsChips: boolean; Option_Options: string; // JSON string + Required: boolean; OrderNumber: number; @@ -112,7 +114,7 @@ export interface FieldDefinition { Time_HasSecond: boolean; - Type: number | string; // → FieldTypes enum عددی تو داری + Type: number; // → FieldTypes enum عددی تو داری Unit: string; diff --git a/src/modules/dashboard/pages/campaigns/detail.tsx b/src/modules/dashboard/pages/campaigns/detail.tsx index db86840..ce5d690 100644 --- a/src/modules/dashboard/pages/campaigns/detail.tsx +++ b/src/modules/dashboard/pages/campaigns/detail.tsx @@ -175,7 +175,8 @@ export function CampaignDetailPage() {

- توسط: {campaign.nickname} + توسط:{" "} + {campaign.user_id_nickname}

diff --git a/src/modules/dashboard/pages/step-form/index.tsx b/src/modules/dashboard/pages/step-form/index.tsx index 269efb0..00b3c25 100644 --- a/src/modules/dashboard/pages/step-form/index.tsx +++ b/src/modules/dashboard/pages/step-form/index.tsx @@ -3,17 +3,25 @@ import { useEffect, useState, type FC } from "react"; import type { WorkflowResponse } from "@/core/types/global.type"; import type { FieldDefinition as LocalFieldDefinition } from "@core/utils/dynamic-field.utils"; import { FieldTypes } from "@core/utils/dynamic-field.utils"; -import { fetchFieldService } from "@modules/dashboard/service/dynamic-form.service"; +import { + fetchFieldIndex, + fetchFielSecondeIndex, +} from "@modules/dashboard/service/dynamic-form.service"; import { useQuery } from "@tanstack/react-query"; -import { useParams } from "react-router-dom"; +import { useSearchParams } from "react-router-dom"; import DynamicForm from "./dynamic-form"; const StepFormPage: FC = () => { const [fields, setFields] = useState([]); - const { id } = useParams<{ id: string }>(); + const [params] = useSearchParams(); + const stageID = params.get("stageID"); + const processID = params.get("processID"); const { data, isLoading, error } = useQuery({ - queryKey: ["dynamic-field", id], - queryFn: fetchFieldService, + queryKey: ["dynamic-field", stageID ?? processID], + queryFn: () => { + if (stageID) return fetchFielSecondeIndex(stageID); + return fetchFieldIndex(processID!); + }, }); useEffect(() => { @@ -54,6 +62,7 @@ const StepFormPage: FC = () => { Description_HasSaving: el.Description_HasSaving, Description_IsParser: el.Description_IsParser, Description_Text: el.Description_Text, + Description: el.Description_Text, Document: el.Document, File_Extentions: el.File_Extentions, File_MaxSize: el.File_MaxSize, @@ -62,6 +71,8 @@ const StepFormPage: FC = () => { LockFirstValue: el.LockFirstValue, NameOrID: el.NameOrID, Option_IsChips: el.Option_IsChips, + // Option_Options: el.Option_Options, + // Required: el.Required, OrderNumber: el.OrderNumber, Parent_CanBeSave: el.Parent_CanBeSave, Parent_DependentFields: el.Parent_DependentFields, @@ -83,7 +94,7 @@ const StepFormPage: FC = () => { Time_HasSecond: el.Time_HasSecond, Unit: el.Unit, ViewPermissionCount: el.ViewPermissionCount, - typeValue: el.Type as FieldTypes, // Map number to enum + typeValue: el.Type as FieldTypes, }; if (el.Option_Options) { diff --git a/src/modules/dashboard/pages/steps/index.tsx b/src/modules/dashboard/pages/steps/index.tsx new file mode 100644 index 0000000..2e3a36c --- /dev/null +++ b/src/modules/dashboard/pages/steps/index.tsx @@ -0,0 +1,224 @@ +import { DASHBOARD_ROUTE } from "@modules/dashboard/routes/route.constant"; +import { getCampaignStepsService } from "@modules/dashboard/service/campaigns.service"; +import { useQuery } from "@tanstack/react-query"; +import { MoveLeft } from "lucide-react"; +import { useEffect, useState, type FC } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import type { CampaignProcess, GroupedCampaign } from "./step.type"; + +const StepsPage: FC = () => { + const { campaignId } = useParams<{ campaignId: string }>(); + const navigate = useNavigate(); + const [steps, setSteps] = useState([]); + const [selectedStepId, setSelectedStepId] = useState(null); + const { data, isLoading, error } = useQuery>({ + queryKey: ["dynamic-step"], + queryFn: () => getCampaignStepsService(campaignId!), + }); + + useEffect(() => { + if (data && Object.keys(data).length > 0) { + const processes: CampaignProcess[] = Object.keys(data[0]) + .filter( + (key) => + key.startsWith("process") && + key.endsWith("_id") === false && + key.includes("_") + ) + .map((key) => { + const index = key.match(/process(\d+)_/)?.[1]; + if (!index) return null; + return { + processId: data[0][`process${index}`], + category: data[0][`process${index}_category`], + score: data[0][`process${index}_score`], + stageId: data[0][`process${index}_stage_id`], + status: data[0][`process${index}_status`], + }; + }) + .filter(Boolean) as CampaignProcess[]; + + const grouped: GroupedCampaign[] = Object.values( + processes.reduce((acc, item) => { + if (!acc[item.category]) { + acc[item.category] = { + category: item.category, + processes: [], + stageID: Number(item.stageId), + processId: Number(item.processId), + }; + } + acc[item.category].processes.push(item); + return acc; + }, {} as Record) + ); + setSteps(grouped); + } + }, [data]); + + const handleBack = () => { + navigate( + `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.campaigns}/${campaignId}` + ); + }; + + // navigate(`${DASHBOARD_ROUTE.dynamicForm}?stageID=${stageID}`); + // navigate(`${DASHBOARD_ROUTE.dynamicForm}?processID=${processID}`); + + const handleStepClick = (step: GroupedCampaign) => { + // setSelectedStepId(step.processId); + + if ( + step.stageID !== null && + step.stageID !== 0 && + step.processId !== null && + step.processId !== 0 + ) { + navigate( + `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.dynamicForm}?stageID=${step.stageID}`, + { + replace: true, + } + ); + } + + if ( + (step.processId != 0 && + step.processId != null && + step.processId != undefined && + step.stageID === null) || + step.stageID === undefined + ) { + navigate( + `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.dynamicForm}?processID=${step.processId}`, + { + replace: true, + } + ); + } + + if ( + (step.stageID != 0 && + step.stageID != null && + step.stageID != undefined && + step.processId === null) || + step.processId === undefined + ) { + navigate( + `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.dynamicForm}?stageID=${step.stageID}`, + { + replace: true, + } + ); + } + }; + + if (isLoading) { + return ( +
+
+ در حال بارگزاری .... +
+
+ ); + } + + if (error) { + return ( +
+
{error.message}
+
+ ); + } + + return ( +
+ {/* Back Button */} +
+ +
+ +

+ مراحل جزیره +

+ +
+ {steps.length === 0 ? ( +

+ مرحله ای یافت نشد. +

+ ) : ( +
    + {steps.map((step, index) => ( +
  • handleStepClick(step)} + tabIndex={0} // Make list item focusable + > +
    + {index + 1} +
    +
    +

    + {step.category} +

    + {step.category && + step.category && + step.category !== step.category && ( +

    + {step.category} +

    + )} +
    +
  • + ))} +
+ )} +
+
+ ); +}; + +export default StepsPage; diff --git a/src/modules/dashboard/pages/steps/step.type.ts b/src/modules/dashboard/pages/steps/step.type.ts new file mode 100644 index 0000000..067390d --- /dev/null +++ b/src/modules/dashboard/pages/steps/step.type.ts @@ -0,0 +1,14 @@ +export interface CampaignProcess { + processId: string; + category: string; + score: string; + stageId: string; + status: string; +} + +export interface GroupedCampaign { + stageID: number; + processId: number; + category: string; + processes: CampaignProcess[]; +} diff --git a/src/modules/dashboard/routes/route.constant.ts b/src/modules/dashboard/routes/route.constant.ts index d45c9fe..be7dffa 100644 --- a/src/modules/dashboard/routes/route.constant.ts +++ b/src/modules/dashboard/routes/route.constant.ts @@ -5,5 +5,6 @@ export const DASHBOARD_ROUTE = { campaigns: "campaigns", campaing: "campaing", joinToCampaing: "join-to-campaing", - daynamicPage: "dynamic-page", + dynamicForm: "dynamic-form", + steps: "steps", }; diff --git a/src/modules/dashboard/routes/router.tsx b/src/modules/dashboard/routes/router.tsx index c6bec61..4a385f1 100644 --- a/src/modules/dashboard/routes/router.tsx +++ b/src/modules/dashboard/routes/router.tsx @@ -6,6 +6,7 @@ import JoinToCampaing from "../pages/join-to-campaing"; import DashboardPage from "../pages/main-page"; import ProfilePage from "../pages/profile"; import StepFormPage from "../pages/step-form"; +import StepsPage from "../pages/steps"; import { DASHBOARD_ROUTE } from "./route.constant"; export const dashboardRoutes: AppRoute[] = [ @@ -34,9 +35,13 @@ export const dashboardRoutes: AppRoute[] = [ element: , }, { - path: `${DASHBOARD_ROUTE.daynamicPage}/:id`, + path: DASHBOARD_ROUTE.dynamicForm, element: , }, + { + path: `${DASHBOARD_ROUTE.steps}`, + element: , + }, ], }, ]; diff --git a/src/modules/dashboard/service/campaigns.service.ts b/src/modules/dashboard/service/campaigns.service.ts index 6052f10..888e437 100644 --- a/src/modules/dashboard/service/campaigns.service.ts +++ b/src/modules/dashboard/service/campaigns.service.ts @@ -323,3 +323,32 @@ export const createCircleService = async ( toast.success("محفل با موفقیت ایجاد شد"); return res.data; }; + +export const getCampaignStepsService = async ( + campaignId: string +): Promise> => { + const query = { + ProcessName: "run_process", + OutputFields: ["*"], + conditions: [ + ["campaign_id", "=", "18909", "and"], + ["group_id", "=", "18950"], + ], + }; + + const [err, res] = await to(api.post(API_ADDRESS.select, query)); + if (err) { + throw err; + } + + if (res.data.resultType !== 0) { + toast.error("خطا در دریافت مراحل کارزار"); + throw new Error("خطا در دریافت مراحل کارزار"); + } + + const data = JSON.parse(res.data.data); + if (!data || Object.keys(data).length === 0) { + return {}; + } + return data; +}; diff --git a/src/modules/dashboard/service/dynamic-form.service.ts b/src/modules/dashboard/service/dynamic-form.service.ts index 0c87a6a..96f7ade 100644 --- a/src/modules/dashboard/service/dynamic-form.service.ts +++ b/src/modules/dashboard/service/dynamic-form.service.ts @@ -4,8 +4,26 @@ import type { WorkflowResponse } from "@/core/types/global.type"; import to from "await-to-js"; import { toast } from "react-toastify"; -export const fetchFieldService = async (): Promise => { - const [err, res] = await to(api.post(API_ADDRESS.index, "1224")); +export const fetchFieldIndex = async ( + id: string +): Promise => { + const [err, res] = await to(api.post(API_ADDRESS.index, id)); + if (err) { + throw err; + } + + if (res.data.resultType !== 0) { + toast.error(res.data.message || "خطا در دریافت فیلدها"); + throw new Error("خطا در دریافت فیلدها"); + } + const data = JSON.parse(res.data.data); + return data; +}; + +export const fetchFielSecondeIndex = async ( + id: string +): Promise => { + const [err, res] = await to(api.post(API_ADDRESS.index, id)); if (err) { throw err; } diff --git a/src/modules/dashboard/types/campaign-steps.type.ts b/src/modules/dashboard/types/campaign-steps.type.ts new file mode 100644 index 0000000..ab6cea7 --- /dev/null +++ b/src/modules/dashboard/types/campaign-steps.type.ts @@ -0,0 +1,12 @@ +export interface CampaignStep { + id: string; + orderNumber: number; + stepName: string; + title: string; // Assuming title is also available, as per description + isSelected?: boolean; // Optional field for highlighting +} + +export interface GetCampaignStepsResponse { + data: CampaignStep[]; + // Add any other relevant fields from the API response +} diff --git a/tsconfig.app.json b/tsconfig.app.json index 8821b2c..2f1a656 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -27,7 +27,7 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "erasableSyntaxOnly": true, + "erasableSyntaxOnly": false, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, diff --git a/tsconfig.node.json b/tsconfig.node.json index 8a67f62..3439137 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -18,7 +18,7 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "erasableSyntaxOnly": true, + "erasableSyntaxOnly": false, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true },