yari-garan/src/modules/dashboard/pages/steps/index.tsx
MehrdadAdabi bbb6bfb7f7 feat: Introduce new dynamic field properties and refine form handling
This commit introduces new capabilities for dynamic forms and refactors related components for improved type safety and data fetching.

- **`src/core/utils/dynamic-field.utils.ts`**:
    - Added `Description` and `Required` properties to the `FieldDefinition` interface to allow for more detailed field configurations and validation.
    - Narrowed the `Type` property from `number | string` to `number` for stricter type enforcement.
- **`src/core/components/base/form-field.tsx`**:
    - Applied non-null assertion operators (`!`) and optional chaining (`?.`) to `field.MinValue`, `field.MaxValue`, `field.Length`, and `field.Type`. This improves type safety and handles cases where these properties might be undefined according to the updated `FieldDefinition`.
- **`src/core/service/api-address.ts`**:
    - Added `index2: "workflow/index2"` to `API_ADDRESS` to support a new workflow data endpoint.
- **`src/modules/dashboard/pages/campaigns/detail.tsx`**:
    - Updated the campaign detail page to display `campaign.user_id_nickname` instead of `campaign.nickname` for accurate user identification.
- **`src/modules/dashboard/pages/step-form/index.tsx`**:
    - Refactored dynamic form data fetching to use `fetchFieldIndex` and `fetchFielSecondeIndex` services.
    - Switched from `useParams` to `useSearchParams` for more flexible parameter handling in the step form.
2025-11-26 18:15:48 +03:30

225 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<GroupedCampaign[]>([]);
const [selectedStepId, setSelectedStepId] = useState<number | null>(null);
const { data, isLoading, error } = useQuery<Record<string, any>>({
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<string, GroupedCampaign>)
);
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 (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="text-lg font-medium text-gray-700">
در حال بارگزاری ....
</div>
</div>
);
}
if (error) {
return (
<div className="flex items-center justify-center min-h-screen bg-red-100 text-red-700">
<div className="text-lg font-medium">{error.message}</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 p-4 sm:p-6 lg:p-8">
{/* Back Button */}
<div className="mb-6">
<button
onClick={handleBack}
className="flex items-center gap-2 text-blue-600 hover:text-blue-800 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 rounded-md p-2"
>
<MoveLeft className="rotate-180" size={20} />
بازگشت به صفحه قبل
</button>
</div>
<h1 className="text-3xl sm:text-4xl font-extrabold text-gray-900 mb-8 text-center">
مراحل جزیره
</h1>
<div className="max-w-2xl mx-auto">
{steps.length === 0 ? (
<p className="text-center text-gray-600 text-lg">
مرحله ای یافت نشد.
</p>
) : (
<ul className="space-y-4">
{steps.map((step, index) => (
<li
key={`${step.category}-${index}`}
className={`
relative flex items-center gap-2.5 p-4 sm:p-6 rounded-lg shadow-md
bg-white border border-gray-200
hover:shadow-lg hover:border-blue-300
focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-opacity-50
transition-all duration-300 ease-in-out transform
${
selectedStepId === step.stageID
? "bg-blue-50 border-blue-500 ring-2 ring-blue-500 ring-opacity-75 scale-105"
: "hover:scale-102"
}
`}
onClick={() => handleStepClick(step)}
tabIndex={0} // Make list item focusable
>
<div
className={`
flex items-center justify-center
w-10 h-10 sm:w-12 sm:h-12 rounded-full
font-bold text-lg sm:text-xl
mr-4 sm:mr-6 shrink-0
${
selectedStepId === step.stageID
? "bg-blue-600 text-white"
: "bg-gray-200 text-gray-700"
}
transition-all duration-300 ease-in-out
`}
>
{index + 1}
</div>
<div className="grow">
<h2
className={`
text-lg sm:text-xl font-semibold
${
selectedStepId === step.stageID
? "text-blue-800"
: "text-gray-800"
}
transition-colors duration-300 ease-in-out
`}
>
{step.category}
</h2>
{step.category &&
step.category &&
step.category !== step.category && (
<p className="text-sm text-gray-500 mt-1">
{step.category}
</p>
)}
</div>
</li>
))}
</ul>
)}
</div>
</div>
);
};
export default StepsPage;