diff --git a/src/app/(main)/projects/copy/not-found.tsx b/src/app/(main)/projects/copy/not-found.tsx
new file mode 100644
index 0000000..14e0e6d
--- /dev/null
+++ b/src/app/(main)/projects/copy/not-found.tsx
@@ -0,0 +1,17 @@
+import { getServerI18n } from "@/i18n";
+import { Stack, Typography, Link } from "@mui/material";
+import NextLink from "next/link";
+
+export default async function NotFound() {
+ const { t } = await getServerI18n("projects", "common");
+
+ return (
+
+ {t("Not Found")}
+ {t("The project was not found!")}
+
+ {t("Return to all projects")}
+
+
+ );
+}
diff --git a/src/app/(main)/projects/copy/page.tsx b/src/app/(main)/projects/copy/page.tsx
new file mode 100644
index 0000000..8ff2777
--- /dev/null
+++ b/src/app/(main)/projects/copy/page.tsx
@@ -0,0 +1,77 @@
+import { fetchAllCustomers, fetchAllSubsidiaries } from "@/app/api/customer";
+import { fetchGrades } from "@/app/api/grades";
+import {
+ fetchProjectBuildingTypes,
+ fetchProjectCategories,
+ fetchProjectContractTypes,
+ fetchProjectDetails,
+ fetchProjectFundingTypes,
+ fetchProjectLocationTypes,
+ fetchProjectServiceTypes,
+ fetchProjectWorkNatures,
+} from "@/app/api/projects";
+import { preloadStaff, preloadTeamLeads } from "@/app/api/staff";
+import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks";
+import { fetchUserAbilities } from "@/app/utils/fetchUtil";
+import { ServerFetchError } from "@/app/utils/fetchUtil";
+import CreateProject from "@/components/CreateProject";
+import { I18nProvider, getServerI18n } from "@/i18n";
+import { MAINTAIN_PROJECT } from "@/middleware";
+import Typography from "@mui/material/Typography";
+import { isArray } from "lodash";
+import { Metadata } from "next";
+import { notFound } from "next/navigation";
+
+interface Props {
+ searchParams: { [key: string]: string | string[] | undefined };
+}
+
+export const metadata: Metadata = {
+ title: "Copy Project",
+};
+
+const Projects: React.FC = async ({ searchParams }) => {
+ const { t } = await getServerI18n("projects");
+ // Assume projectId is string here
+ const projectId = searchParams["id"];
+ const abilities = await fetchUserAbilities()
+
+ if (!projectId || isArray(projectId) || ![MAINTAIN_PROJECT].some(ability => abilities.includes(ability))) {
+ notFound();
+ }
+
+ // Preload necessary dependencies
+ fetchAllTasks();
+ fetchTaskTemplates();
+ fetchProjectCategories();
+ fetchProjectContractTypes();
+ fetchProjectFundingTypes();
+ fetchProjectLocationTypes();
+ fetchProjectServiceTypes();
+ fetchProjectBuildingTypes();
+ fetchProjectWorkNatures();
+ fetchAllCustomers();
+ fetchAllSubsidiaries();
+ fetchGrades();
+ preloadTeamLeads();
+ preloadStaff();
+
+ try {
+ console.log(projectId)
+ await fetchProjectDetails(projectId);
+ } catch (e) {
+ if (e instanceof ServerFetchError && e.response?.status === 404) {
+ notFound();
+ }
+ }
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+export default Projects;
diff --git a/src/app/(main)/projects/copySub/not-found.tsx b/src/app/(main)/projects/copySub/not-found.tsx
new file mode 100644
index 0000000..234e436
--- /dev/null
+++ b/src/app/(main)/projects/copySub/not-found.tsx
@@ -0,0 +1,17 @@
+import { getServerI18n } from "@/i18n";
+import { Stack, Typography, Link } from "@mui/material";
+import NextLink from "next/link";
+
+export default async function NotFound() {
+ const { t } = await getServerI18n("projects", "common");
+
+ return (
+
+ {t("Not Found")}
+ {t("The sub project was not found!")}
+
+ {t("Return to all projects")}
+
+
+ );
+}
diff --git a/src/app/(main)/projects/copySub/page.tsx b/src/app/(main)/projects/copySub/page.tsx
new file mode 100644
index 0000000..13b37da
--- /dev/null
+++ b/src/app/(main)/projects/copySub/page.tsx
@@ -0,0 +1,79 @@
+import { fetchAllCustomers, fetchAllSubsidiaries } from "@/app/api/customer";
+import { fetchGrades } from "@/app/api/grades";
+import {
+ fetchMainProjects,
+ fetchProjectBuildingTypes,
+ fetchProjectCategories,
+ fetchProjectContractTypes,
+ fetchProjectDetails,
+ fetchProjectFundingTypes,
+ fetchProjectLocationTypes,
+ fetchProjectServiceTypes,
+ fetchProjectWorkNatures,
+} from "@/app/api/projects";
+import { preloadStaff, preloadTeamLeads } from "@/app/api/staff";
+import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks";
+import { fetchUserAbilities } from "@/app/utils/fetchUtil";
+import CreateProject from "@/components/CreateProject";
+import { I18nProvider, getServerI18n } from "@/i18n";
+import { MAINTAIN_PROJECT } from "@/middleware";
+import Typography from "@mui/material/Typography";
+import { isArray } from "lodash";
+import { Metadata } from "next";
+import { notFound } from "next/navigation";
+
+interface Props {
+ searchParams: { [key: string]: string | string[] | undefined };
+}
+
+export const metadata: Metadata = {
+ title: "Edit Sub Project",
+};
+
+const Projects: React.FC = async ({ searchParams }) => {
+ const { t } = await getServerI18n("projects");
+ const projectId = searchParams["id"];
+
+ const abilities = await fetchUserAbilities()
+ if (!projectId || isArray(projectId) || !abilities.includes(MAINTAIN_PROJECT)) {
+ notFound();
+ }
+
+ // Preload necessary dependencies
+ fetchAllTasks();
+ fetchTaskTemplates();
+ fetchProjectCategories();
+ fetchProjectContractTypes();
+ fetchProjectFundingTypes();
+ fetchProjectLocationTypes();
+ fetchProjectServiceTypes();
+ fetchProjectBuildingTypes();
+ fetchProjectWorkNatures();
+ fetchAllCustomers();
+ fetchAllSubsidiaries();
+ fetchGrades();
+ preloadTeamLeads();
+ preloadStaff();
+
+ try {
+ await fetchProjectDetails(projectId);
+ const data = await fetchMainProjects();
+
+ if (!Boolean(data) || data.length === 0) {
+ notFound();
+ }
+ } catch (e) {
+ notFound();
+ }
+
+ return (
+ <>
+ {t("Edit Sub Project")}
+
+
+
+ >
+ );
+};
+
+export default Projects;
diff --git a/src/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx
index 444e22e..f4452a9 100644
--- a/src/components/CreateProject/CreateProject.tsx
+++ b/src/components/CreateProject/CreateProject.tsx
@@ -64,6 +64,7 @@ import { deleteDraft, loadDraft, saveToLocalStorage } from "@/app/utils/draftUti
export interface Props {
isEditMode: boolean;
+ isCopyMode: boolean;
draftId?: number;
isSubProject: boolean;
mainProjects?: MainProject[];
@@ -116,6 +117,7 @@ const hasErrorsInTab = (
const CreateProject: React.FC = ({
isEditMode,
+ isCopyMode,
draftId,
isSubProject,
mainProjects,
@@ -546,7 +548,7 @@ const CreateProject: React.FC = ({
}
}, [totalManhour]);
- const loading = isEditMode ? !Boolean(projectName) : false;
+ const loading = isEditMode || isCopyMode ? !Boolean(projectName) : false;
const submitDisabled =
loading ||
diff --git a/src/components/CreateProject/CreateProjectWrapper.tsx b/src/components/CreateProject/CreateProjectWrapper.tsx
index 34c4f07..382a85d 100644
--- a/src/components/CreateProject/CreateProjectWrapper.tsx
+++ b/src/components/CreateProject/CreateProjectWrapper.tsx
@@ -21,17 +21,26 @@ import { fetchGrades } from "@/app/api/grades";
import { fetchUserAbilities, fetchUserStaff } from "@/app/utils/fetchUtil";
type CreateProjectProps = {
- isEditMode: false;
+ isEditMode?: false;
+ isCopyMode?: false;
isSubProject?: boolean;
draftId?: number;
};
interface EditProjectProps {
isEditMode: true;
+ isCopyMode?: false;
projectId?: string;
isSubProject?: boolean;
}
-type Props = CreateProjectProps | EditProjectProps;
+interface CopyProjectProps {
+ isEditMode?: false;
+ isCopyMode: true;
+ projectId?: string;
+ isSubProject?: boolean;
+}
+
+type Props = CreateProjectProps | EditProjectProps | CopyProjectProps;
const CreateProjectWrapper: React.FC = async (props) => {
const [
@@ -79,7 +88,7 @@ const CreateProjectWrapper: React.FC = async (props) => {
(teamLead) => teamLead.teamId === teamId || teamLead.team == "ST",
)
}
- const projectInfo = props.isEditMode
+ const projectInfo = props.isEditMode || props.isCopyMode
? await fetchProjectDetails(props.projectId!)
: undefined;
@@ -87,10 +96,25 @@ const CreateProjectWrapper: React.FC = async (props) => {
? await fetchMainProjects()
: undefined;
+ if (props.isCopyMode && projectInfo) {
+ projectInfo.projectId = null
+ projectInfo.projectCode = projectInfo.projectCode + "-copy"
+ projectInfo.projectName = projectInfo.projectName + "-copy"
+ projectInfo.projectStatus = ""
+ Object.entries(projectInfo.milestones).forEach(([key, value]) => {
+ projectInfo.milestones[Number(key)].payments.forEach(({ ...rest}, idx, orig) => {
+ orig[idx] = { ...rest, id: rest.id * -1 }
+ })
+
+ // console.log(projectInfo.milestones[Number(key)].payments)
+ })
+ }
+
return (
= ({
[router],
);
+ const onProjectCopyClick = useCallback(
+ (project: ProjectResultOrDraft) => {
+ if (!project.isDraft) {
+ if (Boolean(project.mainProject)) {
+ router.push(`/projects/copySub?id=${project.id}`);
+ } else router.push(`/projects/copy?id=${project.id}`);
+ }
+ },
+ [router],
+ );
+
const columns = useMemo[]>(
() => [
{
@@ -138,6 +150,16 @@ const ProjectSearch: React.FC = ({
buttonIcon: ,
disabled: !abilities.includes(MAINTAIN_PROJECT),
},
+ {
+ name: "id",
+ label: t("Copy"),
+ onClick: onProjectCopyClick,
+ buttonIcon: ,
+ disabled: !abilities.includes(MAINTAIN_PROJECT),
+ disabledRows: {
+ status: ["Draft"]
+ }
+ },
{ name: "code", label: t("Project Code") },
{ name: "name", label: t("Project Name") },
{ name: "category", label: t("Project Category") },
diff --git a/src/components/SearchResults/SearchResults.tsx b/src/components/SearchResults/SearchResults.tsx
index 05bdf1c..2fe6bf3 100644
--- a/src/components/SearchResults/SearchResults.tsx
+++ b/src/components/SearchResults/SearchResults.tsx
@@ -35,6 +35,7 @@ interface ColumnWithAction extends BaseColumn {
onClick: (item: T) => void;
buttonIcon: React.ReactNode;
disabled?: boolean;
+ disabledRows?: { [columnName in keyof T]: string[] }; // Filter the row which is going to be disabled
}
export type Column =
@@ -84,6 +85,22 @@ function SearchResults({
setPage(0);
};
+ const disabledRows = (
+ column: ColumnWithAction,
+ item: T
+ ): Boolean => {
+ if (column.disabledRows) {
+ for (const [key, value] of Object.entries(column.disabledRows)) {
+ if (value
+ .map(v => v.toLowerCase())
+ .includes(String(item[key as keyof T]).toLowerCase())
+ ) return true;
+ }
+ }
+
+ return false;
+ };
+
const table = (
<>
@@ -112,7 +129,7 @@ function SearchResults({
column.onClick(item)}
- disabled={Boolean(column.disabled)}
+ disabled={Boolean(column.disabled) || Boolean(disabledRows(column, item))}
>
{column.buttonIcon}