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.
225 lines
7.2 KiB
TypeScript
225 lines
7.2 KiB
TypeScript
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;
|