From 213e865aab5135b61efc095ebec75efb4504a05c Mon Sep 17 00:00:00 2001 From: MehrdadAdabi <126083584+mehrdadAdabi@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:57:36 +0330 Subject: [PATCH] fix: update dropdown search to use name property and improve UI - Fix dropdown filter and display to use option.name instead of option.value - Change disabled input opacity from 50 to 100 for better visibility - Remove default fallback in mobile navbar active item detection - Update campaign join route to include school_code parameter - Refactor join-to-campaign page to use classmate nickname API - Add school_code to campaign service and route parameters - Fix various formatting and whitespace issues --- src/core/components/base/base-drop-down.tsx | 4 +- src/core/components/base/input.tsx | 2 +- src/core/components/others/mobile-navbar.tsx | 2 +- .../dashboard/pages/campaigns/index.tsx | 13 +- .../pages/join-to-campaing/index.tsx | 54 ++++-- .../join-to-campaing/join-to-campaing.ts | 9 +- src/modules/dashboard/pages/profile/index.tsx | 26 ++- .../dashboard/pages/profile/profile.type.ts | 3 +- .../dashboard/pages/step-form/index.tsx | 33 +++- src/modules/dashboard/pages/steps/index.tsx | 11 +- src/modules/dashboard/routes/router.tsx | 4 +- .../dashboard/service/campaigns.service.ts | 180 +++++++++--------- src/modules/dashboard/service/user.service.ts | 3 + 13 files changed, 200 insertions(+), 144 deletions(-) diff --git a/src/core/components/base/base-drop-down.tsx b/src/core/components/base/base-drop-down.tsx index 51cd528..8d2bfab 100644 --- a/src/core/components/base/base-drop-down.tsx +++ b/src/core/components/base/base-drop-down.tsx @@ -125,7 +125,7 @@ export const BaseDropdown = forwardRef( }; const filteredOptions = internalOptions.filter((option) => - option.value.toLowerCase().includes(searchQuery.toLowerCase()) + option.name.toLowerCase().includes(searchQuery.toLowerCase()) ); const hasError = !!error; @@ -193,7 +193,7 @@ export const BaseDropdown = forwardRef( onClick={() => handleSelect(option)} className="cursor-pointer px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" > - {option.value} + {option.name} )) ) : ( diff --git a/src/core/components/base/input.tsx b/src/core/components/base/input.tsx index a7332d4..e9eaed7 100644 --- a/src/core/components/base/input.tsx +++ b/src/core/components/base/input.tsx @@ -33,7 +33,7 @@ const CustomInput = React.forwardRef( )} location.pathname.startsWith(item.path) ); - return matchedItem?.id || items[0]?.id; + return matchedItem?.id; }, [location.pathname, items]); const handleNavClick = (path: string) => { diff --git a/src/modules/dashboard/pages/campaigns/index.tsx b/src/modules/dashboard/pages/campaigns/index.tsx index cc9af77..cd9049b 100644 --- a/src/modules/dashboard/pages/campaigns/index.tsx +++ b/src/modules/dashboard/pages/campaigns/index.tsx @@ -110,7 +110,7 @@ export function CampaignsPage() { const handleJoin = (campaign: Campaign) => { navigate( - `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.joinToCampaing}?id=${campaign.WorkflowID}`, + `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.joinToCampaing}/${campaign.school_code}/${campaign.WorkflowID}`, { replace: true, } @@ -185,11 +185,10 @@ export function CampaignsPage() { @@ -252,7 +251,7 @@ export function CampaignsPage() { variant="info" className="shrink-0" > - انتخاب{" "} + انتخاب )} diff --git a/src/modules/dashboard/pages/join-to-campaing/index.tsx b/src/modules/dashboard/pages/join-to-campaing/index.tsx index 8746956..2517fb3 100644 --- a/src/modules/dashboard/pages/join-to-campaing/index.tsx +++ b/src/modules/dashboard/pages/join-to-campaing/index.tsx @@ -4,16 +4,20 @@ import { CustomButton } from "@/core/components/base/button"; import { CustomInput } from "@/core/components/base/input"; import { createCircleService, - searchUsersService, + getclassmateNickName, } from "@modules/dashboard/service/campaigns.service"; import { useMutation, useQuery } from "@tanstack/react-query"; import { X } from "lucide-react"; import { useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { DASHBOARD_ROUTE } from "../../routes/route.constant"; import type { User } from "./join-to-campaing"; export const JoinToCampaing = () => { const [circleName, setCircleName] = useState(""); - const [searchTerm, setSearchTerm] = useState(""); + const { scoolId, campId } = useParams(); + const navigate = useNavigate() + const [selectedMembers, setSelectedMembers] = useState([]); const [errors, setErrors] = useState<{ circleName?: string; @@ -21,32 +25,41 @@ export const JoinToCampaing = () => { }>({}); const { data: users } = useQuery({ - queryKey: ["users", searchTerm], - queryFn: () => searchUsersService(searchTerm), - enabled: !!searchTerm, + queryKey: ["users", scoolId], + queryFn: () => getclassmateNickName(Number(scoolId)), + enabled: !!scoolId, }); - const { mutate: createCircle, isPending: isCreating } = useMutation({ + + const { mutate: createCircle, isPending: isCreating } = useMutation<{ + group_id: number + }>({ mutationFn: () => createCircleService( circleName, - selectedMembers.map((m) => m.user_id) + String(campId), + selectedMembers.map((m) => m.WorkflowID) ), - onSuccess: () => { + + onSuccess: ({ group_id }) => { setCircleName(""); setSelectedMembers([]); + navigate( + `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.steps}/${campId}/${group_id}` + ); }, }); - const handleAddMember = (userId: string) => { - const user = users?.find((u) => u.user_id === userId); - if (user && !selectedMembers.some((m) => m.user_id === userId)) { + const handleAddMember = (userId: number) => { + const user = users?.find((u) => u.WorkflowID === userId); + if (user && !selectedMembers.some((m) => m.WorkflowID === userId)) { setSelectedMembers([...selectedMembers, user]); } }; - const handleRemoveMember = (userId: string) => { - setSelectedMembers(selectedMembers.filter((m) => m.user_id !== userId)); + + const handleRemoveMember = (userId: number) => { + setSelectedMembers(selectedMembers.filter((m) => m.WorkflowID !== userId)); }; const checkRequired = () => { @@ -91,13 +104,12 @@ export const JoinToCampaing = () => { placeholder="جستجوی لقب عضو..." options={ users?.map((user) => ({ - value: user.user_id, - label: user.nickname, - })) || [] + value: String(user.WorkflowID), + name: user.nickname, + })) ?? [] } - onInputChange={(inputValue) => setSearchTerm(inputValue)} onChange={(value) => { - handleAddMember(value); + handleAddMember(Number(value)); if (errors.members) { setErrors((prev) => ({ ...prev, members: undefined })); } @@ -117,12 +129,12 @@ export const JoinToCampaing = () => {
    {selectedMembers.map((member) => (
  • {member.nickname} + +
    {currentStep?.map((el, idx) => { diff --git a/src/modules/dashboard/pages/steps/index.tsx b/src/modules/dashboard/pages/steps/index.tsx index dfc0abc..78e78c8 100644 --- a/src/modules/dashboard/pages/steps/index.tsx +++ b/src/modules/dashboard/pages/steps/index.tsx @@ -10,11 +10,11 @@ import type { } from "./step.type"; const StepsPage: FC = () => { - const { campaignId } = useParams<{ campaignId: string }>(); + const { campaignId, groupId } = useParams(); const navigate = useNavigate(); const { data, isLoading, error } = useQuery({ queryKey: ["dynamic-step"], - queryFn: () => getCampaignStepsService(), + queryFn: () => getCampaignStepsService(Number(campaignId), Number(groupId)), }); @@ -82,7 +82,7 @@ const StepsPage: FC = () => { const handleBack = () => { navigate( - `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.campaigns}/${campaignId}` + `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.campaigns}` ); }; @@ -90,19 +90,20 @@ const StepsPage: FC = () => { const handleStepClick = (step: GroupedCampaign) => { const filteIncompleteProcess = step.processes.filter(el => el.status === 'انجام نشده') + // `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.steps}/${campId}/${group_id}` if (filteIncompleteProcess.length > 0) { const firstItem = filteIncompleteProcess[0] if (firstItem.stageId) { navigate( - `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.dynamicForm}?stageID=${firstItem.stageId}`, + `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.dynamicForm}?stageID=${firstItem.stageId}&campaignId=${campaignId}&groupId=${groupId}`, { replace: true, }) } if (firstItem.processId) { navigate( - `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.dynamicForm}?processID=${firstItem.processId}`, + `${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.dynamicForm}?processID=${firstItem.processId}&campaignId=${campaignId}&groupId=${groupId}`, { replace: true, } diff --git a/src/modules/dashboard/routes/router.tsx b/src/modules/dashboard/routes/router.tsx index 4a385f1..aeb1171 100644 --- a/src/modules/dashboard/routes/router.tsx +++ b/src/modules/dashboard/routes/router.tsx @@ -31,7 +31,7 @@ export const dashboardRoutes: AppRoute[] = [ element: , }, { - path: `${DASHBOARD_ROUTE.joinToCampaing}/:id`, + path: `${DASHBOARD_ROUTE.joinToCampaing}/:scoolId/:campId`, element: , }, { @@ -39,7 +39,7 @@ export const dashboardRoutes: AppRoute[] = [ element: , }, { - path: `${DASHBOARD_ROUTE.steps}`, + path: `${DASHBOARD_ROUTE.steps}/:campaignId/:groupId`, element: , }, ], diff --git a/src/modules/dashboard/service/campaigns.service.ts b/src/modules/dashboard/service/campaigns.service.ts index 143d1e6..515ec1e 100644 --- a/src/modules/dashboard/service/campaigns.service.ts +++ b/src/modules/dashboard/service/campaigns.service.ts @@ -8,9 +8,9 @@ import type { CreateCampaignData, SignatureItem, } from "@modules/dashboard/pages/campaigns/campaigns.type"; -import type { User } from "@modules/dashboard/pages/join-to-campaing/join-to-campaing"; import to from "await-to-js"; import { toast } from "react-toastify"; +import type { User } from "../pages/join-to-campaing/join-to-campaing"; export const getCampaignsService = async (): Promise => { const userStr = userInfoService.getUserInfo(); @@ -165,6 +165,63 @@ export const getSelectedCampaignsService = async ( return data[0]; }; +export const getCampaignStepsService = async (campId: number, groupId: number) => { + const query = { + ProcessName: "run_process", + OutputFields: ["*"], + conditions: [ + ["campaign_id", "=", campId, "and"], + ["group_id", "=", groupId], + ], + }; + + 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; +}; + +export const getclassmateNickName = async (schoolId: number): Promise> => { + const query = { + ProcessName: "user", + OutputFields: ["nickname"], + "conditions": [ + [ + "school_code", + "=", + schoolId + ] + ] + }; + + 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; +} + export const createCampaignService = async ( data: CreateCampaignData ): Promise => { @@ -245,6 +302,41 @@ export const addCommentService = async ( } }; + +export const createCircleService = async ( + circleName: string, + campId: string, + memberIds: number[] +): Promise<{ group_id: number }> => { + + const members = Object.fromEntries( + Array.from({ length: memberIds.length }, (_, i) => [`member_id${i + 1}`, memberIds[i]]) + ); + const body = { + "group_save_function": { + ...members, + "title": circleName, + "campaign_id": campId + } + } + + const [err, res] = await to(api.post(API_ADDRESS.save, body)); + + if (err) { + toast.error("خطا در ایجاد محفل"); + throw err; + } + + if (res.data.resultType !== 0) { + toast.error(res.data.message || "خطا در ایجاد محفل"); + throw new Error("خطا در ایجاد محفل"); + } + toast.success("محفل با موفقیت ایجاد شد"); + + const parsed = JSON.parse(res.data.data); + return { group_id: Number(parsed.group_id) }; +}; + export const removeCommentService = async ( commentId: number ): Promise => { @@ -264,89 +356,3 @@ export const removeCommentService = async ( throw new Error("خطا در حذف نظر"); } }; - -export const searchUsersService = async (nickname: string): Promise => { - if (!nickname) { - return []; - } - - const query = { - ProcessName: "users", - OutputFields: ["nickname", "user_id"], - conditions: [["nickname", "like", `%${nickname}%`]], - }; - - const [err, res] = await to(api.post(API_ADDRESS.select, query)); - - if (err) { - toast.error("خطا در جستجوی کاربر"); - throw err; - } - - if (res.data.resultType !== 0) { - toast.error("خطا در جستجوی کاربر"); - throw new Error("خطا در جستجوی کاربر"); - } - - const data = JSON.parse(res.data.data); - return data || []; -}; - -export const createCircleService = async ( - circleName: string, - memberIds: string[] -)=> { - const user = userInfoService.getUserInfo(); - - const body = { - ProcessName: "circle", - circle: { - circle_name: circleName, - creator_id: user.username, - status: "فعال", - }, - members: memberIds.map((id) => ({ user_id: id })), - }; - - const [err, res] = await to(api.post(API_ADDRESS.save, body)); - - if (err) { - toast.error("خطا در ایجاد محفل"); - throw err; - } - - if (res.data.resultType !== 0) { - toast.error(res.data.message || "خطا در ایجاد محفل"); - throw new Error("خطا در ایجاد محفل"); - } - - toast.success("محفل با موفقیت ایجاد شد"); - return res.data; -}; - -export const getCampaignStepsService = async () => { - 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/user.service.ts b/src/modules/dashboard/service/user.service.ts index 93ec0ed..668bc57 100644 --- a/src/modules/dashboard/service/user.service.ts +++ b/src/modules/dashboard/service/user.service.ts @@ -20,6 +20,8 @@ export const fetchUserProfile = async () => { "school_code.title", "invitor", "nationalcode", + "user_group.title" + , ], conditions: [["username", "=", person.ID ? person.ID : person.username]], }; @@ -44,6 +46,7 @@ export const fetchUserProfile = async () => { schoolCode: user?.school_code, invitor: user?.invitor, nationalcode: user?.nationalcode, + group: user.user_group_title }; };