From 57eec7c149d791b4086d5d87da0baff125a99613 Mon Sep 17 00:00:00 2001 From: juhanikat Date: Tue, 4 Aug 2026 16:10:05 +0300 Subject: [PATCH] update project update prosess in frontend, and add project validators --- .../src/components/Project/ProjectDetails.tsx | 61 ++++++++++++++++--- .../Project/Tabs/CoordinatorTab.tsx | 4 +- frontend/src/shared/validators/project.ts | 54 ++++++++++++++++ 3 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 frontend/src/shared/validators/project.ts diff --git a/frontend/src/components/Project/ProjectDetails.tsx b/frontend/src/components/Project/ProjectDetails.tsx index ba4112c3..121103af 100755 --- a/frontend/src/components/Project/ProjectDetails.tsx +++ b/frontend/src/components/Project/ProjectDetails.tsx @@ -1,12 +1,20 @@ import { useMemo } from 'react' import { useNavigate, useParams } from 'react-router-dom' -import { CircularProgress } from '@mui/material' -import { DetailView, TabType } from '../DetailView/DetailView' -import { CoordinatorTab } from './Tabs/CoordinatorTab' -import { useDeleteProjectMutation, useGetProjectDetailsQuery, useUpdateProjectMutation } from '@/redux/projectReducer' + import { useNotify } from '@/hooks/notification' import { useUsersApi } from '@/hooks/useUsersApi' -import { mapProjectEditDataToUpdatePayload } from '@/api/projectsApi' +import { + UpdateProjectPayload, + useDeleteProjectMutation, + useGetProjectDetailsQuery, + useUpdateProjectMutation, +} from '@/redux/projectReducer' +import { ProjectPeople, RowState } from '@/shared/types' +import { CircularProgress } from '@mui/material' + +import { DetailView, TabType } from '../DetailView/DetailView' +import { CoordinatorTab } from './Tabs/CoordinatorTab' + import type { EditDataType, ProjectDetailsType } from '@/shared/types' import type { ValidationObject } from '@/shared/validators/validator' import type { FetchBaseQueryError } from '@reduxjs/toolkit/query' @@ -56,15 +64,54 @@ export const ProjectDetails = () => { error: null, }) + const createProjectUpdatePayload = (editData: EditDataType): UpdateProjectPayload | null => { + const userOption = users.find(user => user.initials === editData.contact) + if (!userOption) { + notify('Could not find selected coordinator.', 'error') + return null + } + + const isRemoved = (member: EditDataType) => { + const state = (member as EditDataType & { rowState?: RowState }).rowState + return state === 'removed' || state === 'cancelled' + } + + const memberUserIds = Array.from( + new Set( + (editData.now_proj_people ?? []) + .filter(member => !isRemoved(member)) + .map(member => { + const userIdFromPeople = (member.com_people as { user_id?: number } | undefined)?.user_id + const userIdFromRelation = member.com_people?.user?.user_id + if (typeof userIdFromPeople === 'number') return userIdFromPeople + if (typeof userIdFromRelation === 'number') return userIdFromRelation + return users.find(user => user.initials === member.initials) ?? null + }) + .filter((id): id is number => typeof id === 'number') + ) + ) + return { + pid: editData.pid!, + projectCode: editData.proj_code!, + projectName: editData.proj_name!, + coordinatorUserId: userOption.userId, + projectStatus: editData.proj_status!, + recordStatus: editData.proj_records!, + memberUserIds, + } + } + const onWrite = async (editData: EditDataType) => { if (!projectId) return - const payload = mapProjectEditDataToUpdatePayload(editData, users) + const payload = createProjectUpdatePayload(editData) if (!payload) { - notify('Coordinator selection is required to save project changes.', 'error') return } + console.log(editData) + console.log(payload) + try { await updateProject(payload).unwrap() notify('Saved project successfully.') diff --git a/frontend/src/components/Project/Tabs/CoordinatorTab.tsx b/frontend/src/components/Project/Tabs/CoordinatorTab.tsx index a670764b..e209095d 100755 --- a/frontend/src/components/Project/Tabs/CoordinatorTab.tsx +++ b/frontend/src/components/Project/Tabs/CoordinatorTab.tsx @@ -9,8 +9,8 @@ export const CoordinatorTab = () => { const projectInformation = [ ['Project Id', textField('pid', { type: 'text', readonly: true, disabled: true })], - ['Project Code', textField('proj_code')], - ['Project Name', textField('proj_name')], + ['Project Code', textField('proj_code', { trim: true })], + ['Project Name', textField('proj_name', { trim: true })], ['Coordinator', ], [ 'Project Status', diff --git a/frontend/src/shared/validators/project.ts b/frontend/src/shared/validators/project.ts new file mode 100644 index 00000000..2622e0db --- /dev/null +++ b/frontend/src/shared/validators/project.ts @@ -0,0 +1,54 @@ +import { EditDataType, Project } from '../types' +import { Validators, validateFields, validator } from './validator' + +/* + pid: number; + contact: string; + proj_code: string | null; + proj_name: string | null; + proj_status: string | null; + proj_records: boolean | null; + */ + +const projectValidators: Validators>> = { + pid: { + name: 'Project Id', + required: true, + }, + contact: { + name: 'Coordinator', + required: true, + }, + proj_code: { + name: 'Project Code', + required: true, + }, + proj_name: { + name: 'Project Name', + required: true, + }, + proj_status: { + name: 'Project Status', + required: true, + asString: value => { + if (!['current', 'no_data', 'finished'].includes(value)) + return 'Project Status must be one of the following: "current", "no_data", or "finished".' + return null + }, + }, + proj_records: { + name: 'Record Status', + required: true, + asString: value => { + if (!['public', 'private'].includes(value)) return 'Record Status must be either true or false.' + return null + }, + }, +} + +export const validateProject = (editData: EditDataType, fieldName: keyof EditDataType) => { + return validator>(projectValidators, editData, fieldName) +} + +export const validateProjectFields = (editData: Partial>) => + validateFields>(projectValidators, editData)