Переглянути джерело

Edit Department + Delete

Ediit Postion + Delete
tags/Baseline_30082024_FRONTEND_UAT
MSI\2Fi 1 рік тому
джерело
коміт
6e2266fba2
13 змінених файлів з 175 додано та 61 видалено
  1. +31
    -0
      src/app/(main)/settings/department/edit/page.tsx
  2. +1
    -1
      src/app/(main)/settings/department/new/page.tsx
  3. +17
    -4
      src/app/api/departments/actions.ts
  4. +10
    -0
      src/app/api/departments/index.ts
  5. +24
    -12
      src/app/api/positions/actions.ts
  6. +14
    -8
      src/components/CreateDepartment/CreateDepartment.tsx
  7. +15
    -9
      src/components/CreateDepartment/CreateDepartmentWrapper.tsx
  8. +4
    -4
      src/components/CreateDepartment/DepartmentDetails.tsx
  9. +4
    -4
      src/components/CreatePosition/PositionDetails.tsx
  10. +26
    -3
      src/components/DepartmentSearch/DepartmentSearch.tsx
  11. +1
    -9
      src/components/EditPosition/EditPosition.tsx
  12. +4
    -4
      src/components/EditPosition/PositionDetails.tsx
  13. +24
    -3
      src/components/PositionSearch/PositionSearch.tsx

+ 31
- 0
src/app/(main)/settings/department/edit/page.tsx Переглянути файл

@@ -0,0 +1,31 @@
import CreateDepartment from "@/components/CreateDepartment";
import { I18nProvider, getServerI18n } from "@/i18n";
import Typography from "@mui/material/Typography";
import { Metadata } from "next";

export const metadata: Metadata = {
title: "Create Department",
};

interface Props {
searchParams: { [key: string]: string | undefined };
}

const Department: React.FC<Props> = async ({searchParams}) => {
const { t } = await getServerI18n("departments");

// Preload necessary dependencies
// Assume projectId is string here
const departmentId = searchParams["id"];

return (
<>
<Typography variant="h4">{t("Create Department")}</Typography>
<I18nProvider namespaces={["departments"]}>
<CreateDepartment isEdit={true} departmentId={departmentId}/>
</I18nProvider>
</>
);
};

export default Department;

+ 1
- 1
src/app/(main)/settings/department/new/page.tsx Переглянути файл

@@ -16,7 +16,7 @@ const Department: React.FC = async () => {
<> <>
<Typography variant="h4">{t("Create Department")}</Typography> <Typography variant="h4">{t("Create Department")}</Typography>
<I18nProvider namespaces={["departments"]}> <I18nProvider namespaces={["departments"]}>
<CreateDepartment />
<CreateDepartment isEdit={false} />
</I18nProvider> </I18nProvider>
</> </>
); );


+ 17
- 4
src/app/api/departments/actions.ts Переглянути файл

@@ -1,6 +1,6 @@
"use server" "use server"


import { serverFetchJson } from "@/app/utils/fetchUtil";
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api"; import { BASE_API_URL } from "@/config/api";
import { cache } from "react"; import { cache } from "react";


@@ -14,8 +14,9 @@ export interface combo {
records: comboProp[]; records: comboProp[];
} }
export interface CreateDepartmentInputs { export interface CreateDepartmentInputs {
departmentCode: string;
departmentName: string;
id: number;
code: string;
name: string;
description: string; description: string;
} }


@@ -25,7 +26,19 @@ export const saveDepartment = async (data: CreateDepartmentInputs) => {
body: JSON.stringify(data), body: JSON.stringify(data),
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}); });
};
};

export const deleteDepartment = async (id: number) => {
const department = await serverFetchWithNoContent(
`${BASE_API_URL}/departments/${id}`,
{
method: "DELETE",
headers: { "Content-Type": "application/json" },
},
);

return department
};




export const fetchDepartmentCombo = cache(async () => { export const fetchDepartmentCombo = cache(async () => {


+ 10
- 0
src/app/api/departments/index.ts Переглянути файл

@@ -2,6 +2,7 @@ import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api"; import { BASE_API_URL } from "@/config/api";
import { cache } from "react"; import { cache } from "react";
import "server-only"; import "server-only";
import { CreateDepartmentInputs } from "./actions";


export interface DepartmentResult { export interface DepartmentResult {
id: number; id: number;
@@ -18,4 +19,13 @@ export const fetchDepartments = cache(async () => {
return serverFetchJson<DepartmentResult[]>(`${BASE_API_URL}/departments`, { return serverFetchJson<DepartmentResult[]>(`${BASE_API_URL}/departments`, {
next: { tags: ["departments"] }, next: { tags: ["departments"] },
}); });
});

export const fetchDepartmentDetails = cache(async (departmentId: string) => {
return serverFetchJson<CreateDepartmentInputs>(
`${BASE_API_URL}/departments/departmentDetails/${departmentId}`,
{
next: { tags: [`departmentDetail${departmentId}`] },
},
);
}); });

+ 24
- 12
src/app/api/positions/actions.ts Переглянути файл

@@ -1,6 +1,6 @@
"use server" "use server"


import { serverFetchJson } from "@/app/utils/fetchUtil";
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api"; import { BASE_API_URL } from "@/config/api";
import { cache } from "react"; import { cache } from "react";
import { PositionResult } from "."; import { PositionResult } from ".";
@@ -15,15 +15,15 @@ export interface combo {
} }


export interface CreatePositionInputs { export interface CreatePositionInputs {
positionCode: string;
positionName: string;
code: string;
name: string;
description: string; description: string;
} }


export interface EditPositionInputs { export interface EditPositionInputs {
id: number; id: number;
positionCode: string;
positionName: string;
code: string;
name: string;
description: string; description: string;
} }


@@ -35,13 +35,25 @@ export const savePosition = async (data: CreatePositionInputs) => {
}); });
}; };


export const editPosition = async (data: EditPositionInputs) => {
return serverFetchJson(`${BASE_API_URL}/positions/new`, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
};
export const editPosition = async (data: EditPositionInputs) => {
return serverFetchJson(`${BASE_API_URL}/positions/new`, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
};

export const deletePosition = async (id: number) => {
const position = await serverFetchWithNoContent(
`${BASE_API_URL}/positions/${id}`,
{
method: "DELETE",
headers: { "Content-Type": "application/json" },
},
);

return position
};


export const fetchPositionCombo = cache(async () => { export const fetchPositionCombo = cache(async () => {
return serverFetchJson<combo>(`${BASE_API_URL}/positions/combo`, { return serverFetchJson<combo>(`${BASE_API_URL}/positions/combo`, {


+ 14
- 8
src/components/CreateDepartment/CreateDepartment.tsx Переглянути файл

@@ -22,18 +22,23 @@ import { Error } from "@mui/icons-material";
import { ProjectCategory } from "@/app/api/projects"; import { ProjectCategory } from "@/app/api/projects";
import { Typography } from "@mui/material"; import { Typography } from "@mui/material";
import DepartmentDetails from "./DepartmentDetails"; import DepartmentDetails from "./DepartmentDetails";
import { DepartmentResult } from "@/app/api/departments";


interface Props {
isEdit: Boolean;
department?: CreateDepartmentInputs;
}


const CreateDepartment: React.FC = ({
// allTasks,
// projectCategories,
// taskTemplates,
// teamLeads,
const CreateDepartment: React.FC<Props> = ({
isEdit,
department,
}) => { }) => {
const [serverError, setServerError] = useState(""); const [serverError, setServerError] = useState("");
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();


console.log(department)
const handleCancel = () => { const handleCancel = () => {
router.back(); router.back();
}; };
@@ -62,9 +67,10 @@ const CreateDepartment: React.FC = ({


const formProps = useForm<CreateDepartmentInputs>({ const formProps = useForm<CreateDepartmentInputs>({
defaultValues: { defaultValues: {
departmentCode: "",
departmentName: "",
description: "",
id: department?.id,
code: department?.code,
name: department?.name,
description: department?.description,
}, },
}); });




+ 15
- 9
src/components/CreateDepartment/CreateDepartmentWrapper.tsx Переглянути файл

@@ -1,18 +1,24 @@
import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks";
import CreateDepartment from "./CreateDepartment"; import CreateDepartment from "./CreateDepartment";
import { fetchTeamLeads } from "@/app/api/staff"; import { fetchTeamLeads } from "@/app/api/staff";
import { DepartmentResult, fetchDepartmentDetails } from "@/app/api/departments";


const CreateDepartmentWrapper: React.FC = async () => {
// const [tasks, taskTemplates, DepartmentCategories, teamLeads] =
// await Promise.all([
// fetchAllTasks(),
// fetchTaskTemplates(),
// fetchDepartmentCategories(),
// fetchTeamLeads(),
// ]);
type CreateDepartmentProps = { isEdit: false };
interface EditDepartmentProps {
isEdit: true;
departmentId?: string;
}

type Props = CreateDepartmentProps | EditDepartmentProps;

const CreateDepartmentWrapper: React.FC<Props> = async (props) => {
const departmentInfo = props.isEdit
? await fetchDepartmentDetails(props.departmentId!)
: undefined;


return ( return (
<CreateDepartment
<CreateDepartment isEdit department={departmentInfo}
/> />
); );
}; };


+ 4
- 4
src/components/CreateDepartment/DepartmentDetails.tsx Переглянути файл

@@ -39,20 +39,20 @@ const DepartmentDetails: React.FC = ({
<TextField <TextField
label={t("Department Code")} label={t("Department Code")}
fullWidth fullWidth
{...register("departmentCode", {
{...register("code", {
required: "Department code required!", required: "Department code required!",
})} })}
error={Boolean(errors.departmentCode)}
error={Boolean(errors.code)}
/> />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
label={t("Department Name")} label={t("Department Name")}
fullWidth fullWidth
{...register("departmentName", {
{...register("name", {
required: "Department name required!", required: "Department name required!",
})} })}
error={Boolean(errors.departmentName)}
error={Boolean(errors.name)}
/> />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>


+ 4
- 4
src/components/CreatePosition/PositionDetails.tsx Переглянути файл

@@ -39,20 +39,20 @@ const PositionDetails: React.FC = ({
<TextField <TextField
label={t("Position Code")} label={t("Position Code")}
fullWidth fullWidth
{...register("positionCode", {
{...register("code", {
required: "Position code required!", required: "Position code required!",
})} })}
error={Boolean(errors.positionCode)}
error={Boolean(errors.code)}
/> />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
label={t("Position Name")} label={t("Position Name")}
fullWidth fullWidth
{...register("positionName", {
{...register("name", {
required: "Position name required!", required: "Position name required!",
})} })}
error={Boolean(errors.positionName)}
error={Boolean(errors.name)}
/> />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>


+ 26
- 3
src/components/DepartmentSearch/DepartmentSearch.tsx Переглянути файл

@@ -5,8 +5,11 @@ import SearchBox, { Criterion } from "../SearchBox";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults"; import SearchResults, { Column } from "../SearchResults";
import EditNote from "@mui/icons-material/EditNote"; import EditNote from "@mui/icons-material/EditNote";
import uniq from "lodash/uniq";
import { DepartmentResult } from "@/app/api/departments"; import { DepartmentResult } from "@/app/api/departments";
import { useRouter } from "next/navigation";
import DeleteIcon from '@mui/icons-material/Delete';
import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { deleteDepartment } from "@/app/api/departments/actions";


interface Props { interface Props {
departments: DepartmentResult[]; departments: DepartmentResult[];
@@ -17,6 +20,7 @@ type SearchParamNames = keyof SearchQuery;


const DepartmentSearch: React.FC<Props> = ({ departments }) => { const DepartmentSearch: React.FC<Props> = ({ departments }) => {
const { t } = useTranslation("departments"); const { t } = useTranslation("departments");
const router = useRouter();


const [filteredDepartments, setFilteredDepartments] = useState(departments); const [filteredDepartments, setFilteredDepartments] = useState(departments);


@@ -33,8 +37,20 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => {
setFilteredDepartments(departments); setFilteredDepartments(departments);
}, [departments]); }, [departments]);


const onProjectClick = useCallback((project: DepartmentResult) => {
console.log(project);
const onProjectClick = useCallback((department: DepartmentResult) => {
console.log(department.id)
router.push(`/settings/department/edit?id=${department.id}`);
}, [router]);

const onDeleteClick = useCallback((department: DepartmentResult) => {

deleteDialog(async() => {
await deleteDepartment(department.id)

successDialog("Delete Success", t)

setFilteredDepartments((prev) => prev.filter((obj) => obj.id !== department.id))
}, t)
}, []); }, []);


const columns = useMemo<Column<DepartmentResult>[]>( const columns = useMemo<Column<DepartmentResult>[]>(
@@ -48,6 +64,13 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => {
{ name: "code", label: t("Department Code") }, { name: "code", label: t("Department Code") },
{ name: "name", label: t("Department Name") }, { name: "name", label: t("Department Name") },
{ name: "description", label: t("Department Description") }, { name: "description", label: t("Department Description") },
{
name: "id",
label: t("Delete"),
onClick: onDeleteClick,
buttonIcon: <DeleteIcon />,
color: "error"
},
], ],
[t, onProjectClick], [t, onProjectClick],
); );


+ 1
- 9
src/components/EditPosition/EditPosition.tsx Переглянути файл

@@ -45,16 +45,8 @@ const EditPosition: React.FC = ({
try{ try{
if (positionId !== null && parseInt(positionId) > 0) { if (positionId !== null && parseInt(positionId) > 0) {
const postionDetails = await fetchPositionDetails(parseInt(positionId)) const postionDetails = await fetchPositionDetails(parseInt(positionId))
const updatedArray: EditPositionInputs[] = postionDetails.map((obj) => {
return {
id: obj.id,
positionCode: obj.code,
positionName: obj.name,
description: obj.description
};
});


setPositionDetails(updatedArray[0])
setPositionDetails(postionDetails[0])
} }
} catch (error){ } catch (error){
console.log(error) console.log(error)


+ 4
- 4
src/components/EditPosition/PositionDetails.tsx Переглянути файл

@@ -46,20 +46,20 @@ const PositionDetails: React.FC<Props> = ({
<TextField <TextField
label={t("Position Code")} label={t("Position Code")}
fullWidth fullWidth
{...register("positionCode", {
{...register("code", {
required: "Position code required!", required: "Position code required!",
})} })}
error={Boolean(errors.positionCode)}
error={Boolean(errors.code)}
/> />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<TextField <TextField
label={t("Position Name")} label={t("Position Name")}
fullWidth fullWidth
{...register("positionName", {
{...register("name", {
required: "Position name required!", required: "Position name required!",
})} })}
error={Boolean(errors.positionName)}
error={Boolean(errors.name)}
/> />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>


+ 24
- 3
src/components/PositionSearch/PositionSearch.tsx Переглянути файл

@@ -7,6 +7,9 @@ import SearchResults, { Column } from "../SearchResults";
import EditNote from "@mui/icons-material/EditNote"; import EditNote from "@mui/icons-material/EditNote";
import { PositionResult } from "@/app/api/positions"; import { PositionResult } from "@/app/api/positions";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import DeleteIcon from '@mui/icons-material/Delete';
import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { deletePosition } from "@/app/api/positions/actions";


interface Props { interface Props {
positions: PositionResult[]; positions: PositionResult[];
@@ -34,12 +37,23 @@ const PositionSearch: React.FC<Props> = ({ positions }) => {
setFilteredPositions(positions); setFilteredPositions(positions);
}, [positions]); }, [positions]);


const onPositionClick = useCallback((project: PositionResult) => {
console.log(project);
const id = project.id
const onPositionClick = useCallback((position: PositionResult) => {
console.log(position);
const id = position.id
router.push(`/settings/position/edit?id=${id}`); router.push(`/settings/position/edit?id=${id}`);
}, []); }, []);


const onDeleteClick = useCallback((position: PositionResult) => {

deleteDialog(async() => {
await deletePosition(position.id)

successDialog("Delete Success", t)

setFilteredPositions((prev) => prev.filter((obj) => obj.id !== position.id))
}, t)
}, []);

const columns = useMemo<Column<PositionResult>[]>( const columns = useMemo<Column<PositionResult>[]>(
() => [ () => [
{ {
@@ -51,6 +65,13 @@ const PositionSearch: React.FC<Props> = ({ positions }) => {
{ name: "code", label: t("Position Code") }, { name: "code", label: t("Position Code") },
{ name: "name", label: t("Position Name") }, { name: "name", label: t("Position Name") },
{ name: "description", label: t("Position Description") }, { name: "description", label: t("Position Description") },
{
name: "id",
label: t("Delete"),
onClick: onDeleteClick,
buttonIcon: <DeleteIcon />,
color: "error"
},
], ],
[t, onPositionClick], [t, onPositionClick],
); );


Завантаження…
Відмінити
Зберегти