mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Refactor project create/edit pages into components
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { CircularProgress, Stack, Typography } from '@mui/material'
|
||||
|
||||
import { PermissionDenied } from '@/components/PermissionDenied'
|
||||
import { ProjectForm, type ProjectFormValues } from '@/components/Project/ProjectForm'
|
||||
import { UnsavedChangesProvider } from '@/components/UnsavedChangesProvider'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { useUsersApi } from '@/hooks/useUsersApi'
|
||||
import { useProjectsApi } from '@/hooks/useProjectsApi'
|
||||
import { useUser } from '@/hooks/user'
|
||||
import { Role } from '@/shared/types'
|
||||
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query'
|
||||
|
||||
export const ProjectCreatePage = () => {
|
||||
const user = useUser()
|
||||
const navigate = useNavigate()
|
||||
const { notify } = useNotify()
|
||||
const { users, isLoading: personsLoading, isError: personsError } = useUsersApi()
|
||||
const { createProject, isSubmitting } = useProjectsApi()
|
||||
const [submitError, setSubmitError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'New project'
|
||||
}, [])
|
||||
|
||||
const userOptions = useMemo(() => users, [users])
|
||||
|
||||
if (!user.token) {
|
||||
return (
|
||||
<PermissionDenied
|
||||
title="Sign in to create a project"
|
||||
message="You must be signed in as an administrator to add a project."
|
||||
actionHref="/login"
|
||||
actionLabel="Go to sign in"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (user.role !== Role.Admin) {
|
||||
return (
|
||||
<PermissionDenied
|
||||
title="You do not have access to create projects"
|
||||
message="Only administrators can create new projects."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (personsLoading) {
|
||||
return (
|
||||
<Stack alignItems="center" justifyContent="center" spacing={2} sx={{ mt: 4 }}>
|
||||
<CircularProgress />
|
||||
<Typography>Loading people...</Typography>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
if (personsError) {
|
||||
return (
|
||||
<PermissionDenied
|
||||
title="Unable to load people"
|
||||
message="We could not load users to assign as coordinator or members. Please try again later."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const handleSubmit = async (values: ProjectFormValues) => {
|
||||
setSubmitError(null)
|
||||
try {
|
||||
const createdProject = await createProject({
|
||||
projectCode: values.projectCode.trim(),
|
||||
projectName: values.projectName.trim(),
|
||||
coordinatorUserId: values.coordinatorUserId!,
|
||||
projectStatus: values.projectStatus,
|
||||
recordStatus: values.recordStatus as boolean,
|
||||
memberUserIds: values.memberUserIds.length ? values.memberUserIds : undefined,
|
||||
}).unwrap()
|
||||
|
||||
notify('Project created successfully.')
|
||||
navigate(`/project/${createdProject.pid}`)
|
||||
} catch (error) {
|
||||
const fetchError = error as FetchBaseQueryError
|
||||
const message =
|
||||
fetchError &&
|
||||
typeof fetchError === 'object' &&
|
||||
'data' in fetchError &&
|
||||
fetchError.data &&
|
||||
typeof fetchError.data === 'object' &&
|
||||
'error' in fetchError.data &&
|
||||
typeof fetchError.data.error === 'string'
|
||||
? fetchError.data.error
|
||||
: 'Failed to create project.'
|
||||
setSubmitError(message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<UnsavedChangesProvider>
|
||||
<Stack spacing={3} sx={{ maxWidth: 900, margin: '0 auto' }}>
|
||||
<Typography variant="h4" component="h1">
|
||||
Create Project
|
||||
</Typography>
|
||||
<Typography color="text.secondary">
|
||||
Provide project information and choose the coordinator and members from existing users.
|
||||
</Typography>
|
||||
|
||||
<ProjectForm
|
||||
users={userOptions}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={isSubmitting}
|
||||
serverError={submitError}
|
||||
submitLabel="Create Project"
|
||||
/>
|
||||
</Stack>
|
||||
</UnsavedChangesProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectCreatePage
|
||||
@@ -0,0 +1,133 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { Button, CircularProgress, Stack, Typography } from '@mui/material'
|
||||
|
||||
import { PermissionDenied } from '@/components/PermissionDenied'
|
||||
import { ProjectForm, type ProjectFormValues } from '@/components/Project/ProjectForm'
|
||||
import { UnsavedChangesProvider } from '@/components/UnsavedChangesProvider'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { useProject } from '@/hooks/useProject'
|
||||
import { useUser } from '@/hooks/user'
|
||||
import { useUpdateProjectMutation } from '@/redux/projectReducer'
|
||||
import { Role } from '@/shared/types'
|
||||
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query'
|
||||
|
||||
export const ProjectEditPage = () => {
|
||||
const { id } = useParams()
|
||||
const projectId = id ? Number(id) : null
|
||||
const user = useUser()
|
||||
const navigate = useNavigate()
|
||||
const { notify } = useNotify()
|
||||
const { project, initialValues, users, isLoading, isError, refetch } = useProject(projectId)
|
||||
const [updateProject, { isLoading: isUpdating }] = useUpdateProjectMutation()
|
||||
const [submitError, setSubmitError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (project) {
|
||||
document.title = `Edit project - ${project.proj_name ?? project.pid}`
|
||||
}
|
||||
}, [project])
|
||||
|
||||
if (!user.token) {
|
||||
return (
|
||||
<PermissionDenied
|
||||
title="Sign in to edit a project"
|
||||
message="You must be signed in as an administrator to edit a project."
|
||||
actionHref="/login"
|
||||
actionLabel="Go to sign in"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (user.role !== Role.Admin) {
|
||||
return (
|
||||
<PermissionDenied
|
||||
title="You do not have access to edit projects"
|
||||
message="Only administrators can edit projects."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (isLoading || !initialValues) {
|
||||
return (
|
||||
<Stack alignItems="center" justifyContent="center" spacing={2} sx={{ mt: 4 }}>
|
||||
<CircularProgress />
|
||||
<Typography>Loading project...</Typography>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
if (isError || !project || !initialValues) {
|
||||
return (
|
||||
<Stack spacing={2} alignItems="center" sx={{ mt: 4 }}>
|
||||
<PermissionDenied
|
||||
title="Unable to load project"
|
||||
message="We could not load the project or people data. Please try again later."
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
void refetch()
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const handleSubmit = async (values: ProjectFormValues) => {
|
||||
if (!projectId) return
|
||||
setSubmitError(null)
|
||||
|
||||
try {
|
||||
await updateProject({
|
||||
pid: projectId,
|
||||
projectCode: values.projectCode.trim(),
|
||||
projectName: values.projectName.trim(),
|
||||
coordinatorUserId: values.coordinatorUserId!,
|
||||
projectStatus: values.projectStatus,
|
||||
recordStatus: values.recordStatus as boolean,
|
||||
memberUserIds: values.memberUserIds.length ? values.memberUserIds : undefined,
|
||||
}).unwrap()
|
||||
|
||||
notify('Project updated successfully.')
|
||||
navigate(`/project/${projectId}`)
|
||||
} catch (error) {
|
||||
const fetchError = error as FetchBaseQueryError
|
||||
const message =
|
||||
fetchError &&
|
||||
typeof fetchError === 'object' &&
|
||||
'data' in fetchError &&
|
||||
fetchError.data &&
|
||||
typeof fetchError.data === 'object' &&
|
||||
'error' in fetchError.data &&
|
||||
typeof fetchError.data.error === 'string'
|
||||
? fetchError.data.error
|
||||
: 'Failed to update project.'
|
||||
setSubmitError(message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<UnsavedChangesProvider>
|
||||
<Stack spacing={3} sx={{ maxWidth: 900, margin: '0 auto' }}>
|
||||
<Typography variant="h4" component="h1">
|
||||
Edit Project
|
||||
</Typography>
|
||||
<Typography color="text.secondary">Update project information, coordinator, and members.</Typography>
|
||||
|
||||
<ProjectForm
|
||||
users={users}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={isUpdating}
|
||||
serverError={submitError}
|
||||
initialValues={initialValues}
|
||||
submitLabel="Save Changes"
|
||||
/>
|
||||
</Stack>
|
||||
</UnsavedChangesProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectEditPage
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProjectCreatePage } from './projects/ProjectCreatePage'
|
||||
import { ProjectCreatePage } from '@/components/Project/ProjectCreatePage'
|
||||
|
||||
export const ProjectNewPage = ProjectCreatePage
|
||||
|
||||
|
||||
@@ -1,119 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { CircularProgress, Stack, Typography } from '@mui/material'
|
||||
import { ProjectCreatePage as ProjectCreatePageComponent } from '@/components/Project/ProjectCreatePage'
|
||||
|
||||
import { PermissionDenied } from '@/components/PermissionDenied'
|
||||
import { ProjectForm, ProjectFormValues } from '@/components/Project/ProjectForm'
|
||||
import { UnsavedChangesProvider } from '@/components/UnsavedChangesProvider'
|
||||
import { useUser } from '@/hooks/user'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { useUsersApi } from '@/hooks/useUsersApi'
|
||||
import { Role } from '@/shared/types'
|
||||
import { useProjectsApi } from '@/hooks/useProjectsApi'
|
||||
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query'
|
||||
|
||||
export const ProjectCreatePage = () => {
|
||||
const user = useUser()
|
||||
const navigate = useNavigate()
|
||||
const { notify } = useNotify()
|
||||
const { users, isLoading: personsLoading, isError: personsError } = useUsersApi()
|
||||
const { createProject, isSubmitting } = useProjectsApi()
|
||||
const [submitError, setSubmitError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'New project'
|
||||
}, [])
|
||||
|
||||
const userOptions = useMemo(() => users, [users])
|
||||
|
||||
if (!user.token) {
|
||||
return (
|
||||
<PermissionDenied
|
||||
title="Sign in to create a project"
|
||||
message="You must be signed in as an administrator to add a project."
|
||||
actionHref="/login"
|
||||
actionLabel="Go to sign in"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (user.role !== Role.Admin) {
|
||||
return (
|
||||
<PermissionDenied
|
||||
title="You do not have access to create projects"
|
||||
message="Only administrators can create new projects."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (personsLoading) {
|
||||
return (
|
||||
<Stack alignItems="center" justifyContent="center" spacing={2} sx={{ mt: 4 }}>
|
||||
<CircularProgress />
|
||||
<Typography>Loading people...</Typography>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
if (personsError) {
|
||||
return (
|
||||
<PermissionDenied
|
||||
title="Unable to load people"
|
||||
message="We could not load users to assign as coordinator or members. Please try again later."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const handleSubmit = async (values: ProjectFormValues) => {
|
||||
setSubmitError(null)
|
||||
try {
|
||||
const createdProject = await createProject({
|
||||
projectCode: values.projectCode.trim(),
|
||||
projectName: values.projectName.trim(),
|
||||
coordinatorUserId: values.coordinatorUserId!,
|
||||
projectStatus: values.projectStatus,
|
||||
recordStatus: values.recordStatus as boolean,
|
||||
memberUserIds: values.memberUserIds.length ? values.memberUserIds : undefined,
|
||||
}).unwrap()
|
||||
|
||||
notify('Project created successfully.')
|
||||
navigate(`/project/${createdProject.pid}`)
|
||||
} catch (error) {
|
||||
const fetchError = error as FetchBaseQueryError
|
||||
const message =
|
||||
fetchError &&
|
||||
typeof fetchError === 'object' &&
|
||||
'data' in fetchError &&
|
||||
fetchError.data &&
|
||||
typeof fetchError.data === 'object' &&
|
||||
'error' in fetchError.data &&
|
||||
typeof fetchError.data.error === 'string'
|
||||
? fetchError.data.error
|
||||
: 'Failed to create project.'
|
||||
setSubmitError(message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<UnsavedChangesProvider>
|
||||
<Stack spacing={3} sx={{ maxWidth: 900, margin: '0 auto' }}>
|
||||
<Typography variant="h4" component="h1">
|
||||
Create Project
|
||||
</Typography>
|
||||
<Typography color="text.secondary">
|
||||
Provide project information and choose the coordinator and members from existing users.
|
||||
</Typography>
|
||||
|
||||
<ProjectForm
|
||||
users={userOptions}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={isSubmitting}
|
||||
serverError={submitError}
|
||||
submitLabel="Create Project"
|
||||
/>
|
||||
</Stack>
|
||||
</UnsavedChangesProvider>
|
||||
)
|
||||
}
|
||||
export const ProjectCreatePage = () => <ProjectCreatePageComponent />
|
||||
|
||||
export default ProjectCreatePage
|
||||
|
||||
@@ -1,133 +1,5 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { Button, CircularProgress, Stack, Typography } from '@mui/material'
|
||||
import { ProjectEditPage as ProjectEditPageComponent } from '@/components/Project/ProjectEditPage'
|
||||
|
||||
import { PermissionDenied } from '@/components/PermissionDenied'
|
||||
import { ProjectForm, ProjectFormValues } from '@/components/Project/ProjectForm'
|
||||
import { UnsavedChangesProvider } from '@/components/UnsavedChangesProvider'
|
||||
import { useUser } from '@/hooks/user'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { useProject } from '@/hooks/useProject'
|
||||
import { Role } from '@/shared/types'
|
||||
import { useUpdateProjectMutation } from '@/redux/projectReducer'
|
||||
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query'
|
||||
|
||||
export const ProjectEditPage = () => {
|
||||
const { id } = useParams()
|
||||
const projectId = id ? Number(id) : null
|
||||
const user = useUser()
|
||||
const navigate = useNavigate()
|
||||
const { notify } = useNotify()
|
||||
const { project, initialValues, users, isLoading, isError, refetch } = useProject(projectId)
|
||||
const [updateProject, { isLoading: isUpdating }] = useUpdateProjectMutation()
|
||||
const [submitError, setSubmitError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (project) {
|
||||
document.title = `Edit project - ${project.proj_name ?? project.pid}`
|
||||
}
|
||||
}, [project])
|
||||
|
||||
if (!user.token) {
|
||||
return (
|
||||
<PermissionDenied
|
||||
title="Sign in to edit a project"
|
||||
message="You must be signed in as an administrator to edit a project."
|
||||
actionHref="/login"
|
||||
actionLabel="Go to sign in"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (user.role !== Role.Admin) {
|
||||
return (
|
||||
<PermissionDenied
|
||||
title="You do not have access to edit projects"
|
||||
message="Only administrators can edit projects."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (isLoading || !initialValues) {
|
||||
return (
|
||||
<Stack alignItems="center" justifyContent="center" spacing={2} sx={{ mt: 4 }}>
|
||||
<CircularProgress />
|
||||
<Typography>Loading project...</Typography>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
if (isError || !project || !initialValues) {
|
||||
return (
|
||||
<Stack spacing={2} alignItems="center" sx={{ mt: 4 }}>
|
||||
<PermissionDenied
|
||||
title="Unable to load project"
|
||||
message="We could not load the project or people data. Please try again later."
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
void refetch()
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const handleSubmit = async (values: ProjectFormValues) => {
|
||||
if (!projectId) return
|
||||
setSubmitError(null)
|
||||
|
||||
try {
|
||||
await updateProject({
|
||||
pid: projectId,
|
||||
projectCode: values.projectCode.trim(),
|
||||
projectName: values.projectName.trim(),
|
||||
coordinatorUserId: values.coordinatorUserId!,
|
||||
projectStatus: values.projectStatus,
|
||||
recordStatus: values.recordStatus as boolean,
|
||||
memberUserIds: values.memberUserIds.length ? values.memberUserIds : undefined,
|
||||
}).unwrap()
|
||||
|
||||
notify('Project updated successfully.')
|
||||
navigate(`/project/${projectId}`)
|
||||
} catch (error) {
|
||||
const fetchError = error as FetchBaseQueryError
|
||||
const message =
|
||||
fetchError &&
|
||||
typeof fetchError === 'object' &&
|
||||
'data' in fetchError &&
|
||||
fetchError.data &&
|
||||
typeof fetchError.data === 'object' &&
|
||||
'error' in fetchError.data &&
|
||||
typeof fetchError.data.error === 'string'
|
||||
? fetchError.data.error
|
||||
: 'Failed to update project.'
|
||||
setSubmitError(message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<UnsavedChangesProvider>
|
||||
<Stack spacing={3} sx={{ maxWidth: 900, margin: '0 auto' }}>
|
||||
<Typography variant="h4" component="h1">
|
||||
Edit Project
|
||||
</Typography>
|
||||
<Typography color="text.secondary">Update project information, coordinator, and members.</Typography>
|
||||
|
||||
<ProjectForm
|
||||
users={users}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={isUpdating}
|
||||
serverError={submitError}
|
||||
initialValues={initialValues}
|
||||
submitLabel="Save Changes"
|
||||
/>
|
||||
</Stack>
|
||||
</UnsavedChangesProvider>
|
||||
)
|
||||
}
|
||||
export const ProjectEditPage = () => <ProjectEditPageComponent />
|
||||
|
||||
export default ProjectEditPage
|
||||
|
||||
Reference in New Issue
Block a user