diff --git a/src/admin/apiProvider/utils/entryFormat.ts b/src/admin/apiProvider/utils/entryFormat.ts
index 929eef6a5..e02829c6e 100644
--- a/src/admin/apiProvider/utils/entryFormat.ts
+++ b/src/admin/apiProvider/utils/entryFormat.ts
@@ -26,7 +26,7 @@ const isDateType = (value: any) => {
return isValid(parseISO(value));
};
-const convertDateFormat = (value: any) => {
+export const convertDateFormat = (value: any) => {
if (typeof value === "string") {
const dateObject = new Date(value);
const formattedDay = dateObject.getUTCDate().toString().padStart(2, "0");
diff --git a/src/admin/components/ResourceTabs/AuditLogTab/AuditLogTab.tsx b/src/admin/components/ResourceTabs/AuditLogTab/AuditLogTab.tsx
index 691a02bd9..967ddd7e3 100644
--- a/src/admin/components/ResourceTabs/AuditLogTab/AuditLogTab.tsx
+++ b/src/admin/components/ResourceTabs/AuditLogTab/AuditLogTab.tsx
@@ -1,94 +1,104 @@
-import { Typography } from "@mui/material";
-import { FC } from "react";
-import {
- Datagrid,
- DateField,
- FunctionField,
- Pagination,
- ReferenceField,
- ReferenceManyField,
- TabbedShowLayout,
- TabProps,
- useShowContext
-} from "react-admin";
+import { Grid, Stack } from "@mui/material";
+import { FC, useEffect, useState } from "react";
+import { Button, Link, TabbedShowLayout, TabProps, useBasename, useShowContext } from "react-admin";
import { When } from "react-if";
import modules from "@/admin/modules";
-import { V2AdminUserRead } from "@/generated/apiSchemas";
-import { Entity } from "@/types/common";
+import Text from "@/components/elements/Text/Text";
+import { PROJECT, SITE } from "@/constants/entities";
+import useAuditLogActions from "@/hooks/AuditStatus/useAuditLogActions";
+
+import AuditLogSiteTabSelection from "./components/AuditLogSiteTabSelection";
+import SiteAuditLogEntityStatus from "./components/SiteAuditLogEntityStatus";
+import SiteAuditLogEntityStatusSide from "./components/SiteAuditLogEntityStatusSide";
+import SiteAuditLogProjectStatus from "./components/SiteAuditLogProjectStatus";
+import { AuditLogButtonStates } from "./constants/enum";
interface IProps extends Omit {
label?: string;
- entity?: Entity["entityName"];
}
-interface FeedbackProps {
- comment: string | undefined;
-}
+const AuditLogTab: FC = ({ label, ...rest }) => {
+ const [buttonToogle, setButtonToogle] = useState(AuditLogButtonStates.PROJECT);
+ const { record, isLoading } = useShowContext();
+ const basename = useBasename();
-const Feedback: FC = ({ comment }) => {
- if (comment == null) {
- return <>->;
- }
+ const {
+ mutateEntity,
+ valuesForStatus,
+ statusLabels,
+ entityType,
+ entityListItem,
+ loadEntityList,
+ selected,
+ setSelected,
+ auditLogData,
+ refetch,
+ checkPolygonsSite
+ } = useAuditLogActions({
+ record,
+ buttonToogle,
+ entityLevel: record?.project ? SITE : PROJECT
+ });
- return (
- <>
- {comment.split("\n").map(fragment => (
- <>
- {fragment}
-
- >
- ))}
- >
- );
-};
-
-const AuditLogTab: FC = ({ label, entity, ...rest }) => {
- const ctx = useShowContext();
- const resource = entity ?? ctx.resource;
+ useEffect(() => {
+ refetch();
+ loadEntityList();
+ }, [buttonToogle]);
return (
-
+
-
- Audit Log
-
- }
- reference={modules.audit.ResourceName}
- filter={{ entity: resource }}
- target="uuid"
- label=""
- >
-
-
-
- `${record?.first_name || ""} ${record?.last_name || ""}`}
- />
-
- {
- const str: string = record?.new_values?.status ?? record?.event ?? "";
-
- return str.replaceAll("-", " ");
+
+
+
+
+
+ Project Status
+
+ Update the site status, view updates, or add comments
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ refetch();
+ loadEntityList();
}}
+ record={selected}
+ polygonList={entityListItem}
+ selectedPolygon={selected}
+ setSelectedPolygon={setSelected}
+ auditLogData={auditLogData?.data}
+ checkPolygonsSite={checkPolygonsSite}
/>
- }
- />
-
-
+
+
);
diff --git a/src/admin/components/ResourceTabs/AuditLogTab/components/AuditLogSiteTabSelection.tsx b/src/admin/components/ResourceTabs/AuditLogTab/components/AuditLogSiteTabSelection.tsx
new file mode 100644
index 000000000..b994c99d5
--- /dev/null
+++ b/src/admin/components/ResourceTabs/AuditLogTab/components/AuditLogSiteTabSelection.tsx
@@ -0,0 +1,26 @@
+import { FC } from "react";
+
+import Button from "@/components/elements/Button/Button";
+
+interface AuditLogSiteTabSelectionProps {
+ buttonToogle: number;
+ setButtonToogle: (buttonToogle: number) => void;
+}
+
+const tabNames = ["Project Status", "Site Status", "Polygon Status"];
+
+const AuditLogSiteTabSelection: FC = ({ buttonToogle, setButtonToogle }) => (
+
+ {tabNames.map((tabName, index) => (
+
+ ))}
+
+);
+
+export default AuditLogSiteTabSelection;
diff --git a/src/admin/components/ResourceTabs/AuditLogTab/components/AuditLogTable.tsx b/src/admin/components/ResourceTabs/AuditLogTab/components/AuditLogTable.tsx
new file mode 100644
index 000000000..929238a6b
--- /dev/null
+++ b/src/admin/components/ResourceTabs/AuditLogTab/components/AuditLogTable.tsx
@@ -0,0 +1,70 @@
+import { FC, Fragment } from "react";
+
+import { convertDateFormat } from "@/admin/apiProvider/utils/entryFormat";
+import Text from "@/components/elements/Text/Text";
+import { AuditStatusResponse, V2FileRead } from "@/generated/apiSchemas";
+
+const formattedTextStatus = (text: string) => {
+ return text.replace(/-/g, " ").replace(/\b\w/g, char => char.toUpperCase());
+};
+
+const getTextForActionTable = (item: { type: string; status: string; request_removed: boolean }): string => {
+ if (item.type === "comment") {
+ return "New Comment";
+ } else if (item.type === "status") {
+ return `New Status: ${formattedTextStatus(item.status)}`;
+ } else if (item.request_removed) {
+ return "Change Request Removed";
+ } else {
+ return "Change Requested Added";
+ }
+};
+
+const columnTitles = ["Date", "User", "Action", "Comments", "Attachments"];
+
+const AuditLogTable: FC<{ auditLogData: { data: AuditStatusResponse[] } }> = ({ auditLogData }) => (
+ <>
+
+ {columnTitles.map(title => (
+
+ {title}
+
+ ))}
+
+
+ {auditLogData?.data?.map((item: AuditStatusResponse, index: number) => (
+
+
+ {convertDateFormat(item?.date_created)}
+
+
+ {`${item.first_name} ${item.last_name}`}
+
+
+ {getTextForActionTable(item as { type: string; status: string; request_removed: boolean })}
+
+
+ {item.comment ?? "-"}
+
+
+ {item?.attachments?.map((attachmentItem: V2FileRead) => (
+ {
+ attachmentItem.url && window.open(attachmentItem.url, "_blank");
+ }}
+ >
+ {attachmentItem.file_name}
+
+ ))}
+
+
+ ))}
+
+ >
+);
+
+export default AuditLogTable;
diff --git a/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogEntityStatus.tsx b/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogEntityStatus.tsx
new file mode 100644
index 000000000..1311e1463
--- /dev/null
+++ b/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogEntityStatus.tsx
@@ -0,0 +1,74 @@
+import { FC } from "react";
+import { Link as RaLink, useBasename } from "react-admin";
+import { When } from "react-if";
+
+import modules from "@/admin/modules";
+import Text from "@/components/elements/Text/Text";
+import { AuditStatusResponse } from "@/generated/apiSchemas";
+
+import CommentarySection from "../../PolygonReviewTab/components/CommentarySection/CommentarySection";
+import { AuditLogButtonStates } from "../constants/enum";
+import { AuditLogEntity } from "../constants/types";
+import AuditLogTable from "./AuditLogTable";
+
+export interface SiteAuditLogEntityStatusProps {
+ entityType: AuditLogEntity;
+ record: SelectedItem | null;
+ auditLogData?: { data: AuditStatusResponse[] };
+ refresh: () => void;
+ buttonToogle: number;
+}
+
+interface SelectedItem {
+ title?: string | undefined;
+ name?: string | undefined;
+ uuid?: string | undefined;
+ value?: string | undefined;
+ meta?: string | undefined;
+ status?: string | undefined;
+}
+
+const SiteAuditLogEntityStatus: FC = ({
+ entityType,
+ record,
+ auditLogData,
+ refresh,
+ buttonToogle
+}) => {
+ const isSite = buttonToogle === AuditLogButtonStates.SITE;
+ const basename = useBasename();
+
+ const getTitle = () => record?.title ?? record?.name;
+
+ return (
+
+
+
+ {entityType} Status and Comments
+
+
+ Update the {entityType?.toLowerCase()} status, view updates, or add comments
+
+
+
+
+ {!isSite && History and Discussion for {getTitle()}}
+ {isSite && (
+
+
+ {getTitle()}
+
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default SiteAuditLogEntityStatus;
diff --git a/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogEntityStatusSide.tsx b/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogEntityStatusSide.tsx
new file mode 100644
index 000000000..1c760afa6
--- /dev/null
+++ b/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogEntityStatusSide.tsx
@@ -0,0 +1,129 @@
+import classNames from "classnames";
+import { Dispatch, SetStateAction, useMemo, useState } from "react";
+import { When } from "react-if";
+
+import Dropdown from "@/components/elements/Inputs/Dropdown/Dropdown";
+import Notification from "@/components/elements/Notification/Notification";
+import StepProgressbar from "@/components/elements/ProgressBar/StepProgressbar/StepProgressbar";
+import Text from "@/components/elements/Text/Text";
+import { usePostV2AuditStatusENTITYUUID } from "@/generated/apiComponents";
+import { AuditStatusResponse } from "@/generated/apiSchemas";
+import { SelectedItem } from "@/hooks/AuditStatus/useLoadEntityList";
+import { recentRequestData } from "@/utils/statusUtils";
+
+import StatusDisplay from "../../PolygonReviewTab/components/PolygonStatus/StatusDisplay";
+import { AuditLogEntity } from "../constants/types";
+import { getRequestPathParam } from "../utils/util";
+
+const SiteAuditLogEntityStatusSide = ({
+ refresh,
+ record,
+ polygonList,
+ selectedPolygon,
+ setSelectedPolygon,
+ auditLogData,
+ entityType = "Polygon",
+ mutate,
+ getValueForStatus,
+ progressBarLabels,
+ tab,
+ checkPolygonsSite
+}: {
+ entityType: AuditLogEntity;
+ refresh?: () => void;
+ record?: any;
+ polygonList?: any[];
+ selectedPolygon?: SelectedItem | null;
+ setSelectedPolygon?: Dispatch> | null;
+ auditLogData?: AuditStatusResponse[];
+ mutate?: any;
+ getValueForStatus?: (status: string) => number;
+ progressBarLabels?: Array<{ id: string; label: string }>;
+ tab?: string;
+ checkPolygonsSite?: boolean | undefined;
+}) => {
+ const [open, setOpen] = useState(false);
+
+ const recentRequest = useMemo(() => {
+ return auditLogData?.find((item: AuditStatusResponse) => item.type == "change-request" && item.is_active);
+ }, [auditLogData]);
+
+ const mutateUpload = entityType === "Project" ? usePostV2AuditStatusENTITYUUID : usePostV2AuditStatusENTITYUUID;
+ const { mutate: upload } = mutateUpload({
+ onSuccess: () => {
+ setOpen(true);
+ setTimeout(() => {
+ setOpen(false);
+ }, 3000);
+ refresh?.();
+ }
+ });
+
+ const deactivateRecentRequest = async () => {
+ upload?.({
+ pathParams: {
+ uuid: record?.uuid,
+ entity: getRequestPathParam(entityType)
+ },
+ body: {
+ status: "",
+ comment: "",
+ type: "change-request",
+ request_removed: true
+ }
+ });
+ };
+
+ return (
+
+
+ {
+ setSelectedPolygon && setSelectedPolygon(polygonList?.find(item => item?.uuid === e[0]));
+ }}
+ />
+
+
{`${entityType} Status`}
+
+
+
+
+
+ Change Requested
+
+
+
{recentRequestData(recentRequest!)}
+
+
{recentRequest?.comment}
+
+
+
+
+
+ );
+};
+
+export default SiteAuditLogEntityStatusSide;
diff --git a/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogProjectStatus.tsx b/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogProjectStatus.tsx
new file mode 100644
index 000000000..1b9195e7c
--- /dev/null
+++ b/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogProjectStatus.tsx
@@ -0,0 +1,28 @@
+import { FC } from "react";
+
+import Text from "@/components/elements/Text/Text";
+import { AuditStatusResponse, ProjectLiteRead } from "@/generated/apiSchemas";
+
+import AuditLogTable from "./AuditLogTable";
+
+export interface SiteAuditLogProjectStatusProps {
+ record?: ProjectLiteRead | null;
+ auditLogData?: { data: AuditStatusResponse[] };
+}
+
+const SiteAuditLogProjectStatus: FC = ({ record, auditLogData }) => (
+
+
+
+ Project Status and Comments
+
+
+ Update the project status, view updates, or add comments
+
+
+
History and Discussion for {record && record?.name}
+ {auditLogData &&
}
+
+);
+
+export default SiteAuditLogProjectStatus;
diff --git a/src/admin/components/ResourceTabs/AuditLogTab/constants/enum.ts b/src/admin/components/ResourceTabs/AuditLogTab/constants/enum.ts
new file mode 100644
index 000000000..3c5d72bf8
--- /dev/null
+++ b/src/admin/components/ResourceTabs/AuditLogTab/constants/enum.ts
@@ -0,0 +1,6 @@
+/* eslint-disable no-unused-vars */
+export enum AuditLogButtonStates {
+ PROJECT = 0,
+ SITE = 1,
+ POLYGON = 2
+}
diff --git a/src/admin/components/ResourceTabs/AuditLogTab/constants/types.ts b/src/admin/components/ResourceTabs/AuditLogTab/constants/types.ts
new file mode 100644
index 000000000..cbf490f1b
--- /dev/null
+++ b/src/admin/components/ResourceTabs/AuditLogTab/constants/types.ts
@@ -0,0 +1 @@
+export type AuditLogEntity = "Project" | "Site" | "Polygon";
diff --git a/src/admin/components/ResourceTabs/AuditLogTab/utils/util.ts b/src/admin/components/ResourceTabs/AuditLogTab/utils/util.ts
new file mode 100644
index 000000000..eba1a7a61
--- /dev/null
+++ b/src/admin/components/ResourceTabs/AuditLogTab/utils/util.ts
@@ -0,0 +1,8 @@
+import { AuditLogEntity } from "../constants/types";
+
+export const getRequestPathParam = (entityType: AuditLogEntity) => {
+ if (entityType === "Polygon") {
+ return "site-polygon";
+ }
+ return entityType.toLocaleLowerCase();
+};
diff --git a/src/admin/components/ResourceTabs/PolygonReviewTab/components/CommentarySection/CommentarySection.tsx b/src/admin/components/ResourceTabs/PolygonReviewTab/components/CommentarySection/CommentarySection.tsx
index 44fbbb71c..15ec3b9e2 100644
--- a/src/admin/components/ResourceTabs/PolygonReviewTab/components/CommentarySection/CommentarySection.tsx
+++ b/src/admin/components/ResourceTabs/PolygonReviewTab/components/CommentarySection/CommentarySection.tsx
@@ -5,16 +5,18 @@ import Text from "@/components/elements/Text/Text";
import Loader from "@/components/generic/Loading/Loader";
import { useGetAuthMe } from "@/generated/apiComponents";
+import { AuditLogEntity } from "../../../AuditLogTab/constants/types";
+
const CommentarySection = ({
- refresh,
record,
entity,
+ refresh,
viewCommentsList = true,
loading = false
}: {
+ record: any;
+ entity: AuditLogEntity;
refresh?: () => void;
- record?: any;
- entity?: "Project" | "SitePolygon" | "Site";
viewCommentsList?: boolean;
loading?: boolean;
}) => {
diff --git a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/PolygonDrawer.tsx b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/PolygonDrawer.tsx
index f185d5f06..a3ec9fa30 100644
--- a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/PolygonDrawer.tsx
+++ b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonDrawer/PolygonDrawer.tsx
@@ -195,7 +195,7 @@ const PolygonDrawer = ({
tab="polygonReview"
checkPolygonsSite={isValidCriteriaData(criteriaValidation)}
/>
-
+
diff --git a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonStatus/StatusDisplay.tsx b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonStatus/StatusDisplay.tsx
index fdbaccb61..aa6fac8a9 100644
--- a/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonStatus/StatusDisplay.tsx
+++ b/src/admin/components/ResourceTabs/PolygonReviewTab/components/PolygonStatus/StatusDisplay.tsx
@@ -8,30 +8,29 @@ import Text from "@/components/elements/Text/Text";
import ModalConfirm from "@/components/extensive/Modal/ModalConfirm";
import { useModalContext } from "@/context/modal.provider";
+import { AuditLogEntity } from "../../../AuditLogTab/constants/types";
+import { getRequestPathParam } from "../../../AuditLogTab/utils/util";
+
const menuPolygonOptions = [
{
title: "Draft",
status: "draft",
- value: 1,
- viewPd: false
+ value: 1
},
{
title: "Submitted",
status: "submitted",
- value: 2,
- viewPd: true
+ value: 2
},
{
title: "Needs More Information",
status: "needs-more-information",
- value: 3,
- viewPd: false
+ value: 3
},
{
title: "Approved",
status: "approved",
- value: 4,
- viewPd: false
+ value: 4
}
];
const menuSiteOptions = [
@@ -94,7 +93,7 @@ const menuProjectOptions = [
];
export interface StatusProps {
- titleStatus: "Site" | "Project" | "Polygon";
+ titleStatus: AuditLogEntity;
mutate?: any;
record?: any;
refresh?: () => void;
@@ -102,7 +101,6 @@ export interface StatusProps {
refetchPolygon?: () => void;
tab?: string;
checkPolygonsSite?: boolean | undefined;
- viewPD?: boolean;
}
const menuOptionsMap = {
@@ -130,8 +128,7 @@ const StatusDisplay = ({
name,
record,
checkPolygonsSite,
- tab,
- viewPD
+ tab
}: StatusProps) => {
const { refetch: reloadEntity } = useShowContext();
const [notificationStatus, setNotificationStatus] = useState<{
@@ -157,16 +154,20 @@ const StatusDisplay = ({
{DescriptionRequestMap[titleStatus]} {name}?
);
- const filterViewPd = viewPD
- ? menuOptionsMap[titleStatus].filter(option => option.viewPd === true)
- : menuOptionsMap[titleStatus];
+
+ const onFinallyRequest = () => {
+ refresh?.();
+ reloadEntity();
+ closeModal();
+ };
+
const openFormModalHandlerStatus = () => {
openModal(
option.value === opt[0]);
try {
await mutate({
- pathParams: { uuid: record?.uuid },
+ pathParams: {
+ uuid: record?.uuid,
+ entity: getRequestPathParam(titleStatus)
+ },
body: {
status: option?.status,
comment: text,
@@ -212,9 +216,7 @@ const StatusDisplay = ({
}, 3000);
console.error(e);
} finally {
- refresh?.();
- reloadEntity?.();
- closeModal?.();
+ onFinallyRequest();
}
}}
/>
@@ -228,13 +230,12 @@ const StatusDisplay = ({
content={contentRequest}
commentArea
onClose={closeModal}
- onConfirm={async (text: any, opt) => {
- const option = menuOptionsMap[titleStatus].find(option => option.value === opt[0]);
+ onConfirm={async (text: any) => {
try {
await mutate({
- pathParams: { uuid: record?.uuid },
+ pathParams: { uuid: record?.uuid, entity: getRequestPathParam(titleStatus) },
body: {
- status: option?.status,
+ status: "",
comment: text,
type: "change-request",
is_active: true,
@@ -272,9 +273,7 @@ const StatusDisplay = ({
}, 3000);
console.error(e);
} finally {
- refresh?.();
- reloadEntity?.();
- closeModal?.();
+ onFinallyRequest();
}
}}
/>
diff --git a/src/admin/modules/nurseryReports/components/NurseryReportShow.tsx b/src/admin/modules/nurseryReports/components/NurseryReportShow.tsx
index 0efd62cde..945586e52 100644
--- a/src/admin/modules/nurseryReports/components/NurseryReportShow.tsx
+++ b/src/admin/modules/nurseryReports/components/NurseryReportShow.tsx
@@ -20,7 +20,7 @@ const NurseryReportShow: FC = () => {
-
+
);
diff --git a/src/admin/modules/projectReports/components/ProjectReportShow.tsx b/src/admin/modules/projectReports/components/ProjectReportShow.tsx
index ae3069cde..6dc74f3e9 100644
--- a/src/admin/modules/projectReports/components/ProjectReportShow.tsx
+++ b/src/admin/modules/projectReports/components/ProjectReportShow.tsx
@@ -20,7 +20,7 @@ const ProjectReportShow: FC = () => {
-
+
);
diff --git a/src/admin/modules/siteReports/components/SiteReportShow.tsx b/src/admin/modules/siteReports/components/SiteReportShow.tsx
index 5eeff841f..1619e161c 100644
--- a/src/admin/modules/siteReports/components/SiteReportShow.tsx
+++ b/src/admin/modules/siteReports/components/SiteReportShow.tsx
@@ -20,7 +20,7 @@ const SiteReportShow: FC = () => {
-
+
);
diff --git a/src/components/elements/CommentaryBox/CommentaryBox.stories.tsx b/src/components/elements/CommentaryBox/CommentaryBox.stories.tsx
index 602763c3c..55829e34a 100644
--- a/src/components/elements/CommentaryBox/CommentaryBox.stories.tsx
+++ b/src/components/elements/CommentaryBox/CommentaryBox.stories.tsx
@@ -1,20 +1,23 @@
import { Meta, StoryObj } from "@storybook/react";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { CommentaryBoxProps as Props } from "./CommentaryBox";
import Component from "./CommentaryBox";
-
const meta: Meta = {
title: "Components/Elements/CommentaryBox",
component: Component
};
-
export default meta;
type Story = StoryObj;
+const client = new QueryClient();
+
export const Default: Story = {
render: (args: Props) => (
-
+
+
+
),
args: {
diff --git a/src/components/elements/CommentaryBox/CommentaryBox.tsx b/src/components/elements/CommentaryBox/CommentaryBox.tsx
index 8a05ee505..477d01afa 100644
--- a/src/components/elements/CommentaryBox/CommentaryBox.tsx
+++ b/src/components/elements/CommentaryBox/CommentaryBox.tsx
@@ -2,10 +2,18 @@ import { useT } from "@transifex/react";
import { useState } from "react";
import { When } from "react-if";
+import { AuditLogEntity } from "@/admin/components/ResourceTabs/AuditLogTab/constants/types";
+import { getRequestPathParam } from "@/admin/components/ResourceTabs/AuditLogTab/utils/util";
import Button from "@/components/elements/Button/Button";
import TextArea from "@/components/elements/Inputs/textArea/TextArea";
import Text from "@/components/elements/Text/Text";
import Icon, { IconNames } from "@/components/extensive/Icon/Icon";
+import {
+ fetchPostV2FileUploadMODELCOLLECTIONUUID,
+ PostV2AuditStatusENTITYUUIDRequestBody,
+ usePostV2AuditStatusENTITYUUID
+} from "@/generated/apiComponents";
+import { AuditStatusResponse } from "@/generated/apiSchemas";
import Notification from "../Notification/Notification";
@@ -16,19 +24,46 @@ export interface CommentaryBoxProps {
mutate?: any;
refresh?: () => void;
record?: any;
- entity?: string;
+ entity?: AuditLogEntity;
}
const CommentaryBox = (props: CommentaryBoxProps) => {
- const { name, lastName, buttonSendOnBox, record, entity } = props;
+ const { name, lastName, buttonSendOnBox } = props;
+ const t = useT();
+
+ const { mutate: sendCommentary } = usePostV2AuditStatusENTITYUUID({
+ onSuccess: (res: AuditStatusResponse) => {
+ const resAuditlog = res as { data: { uuid: string } };
+ const bodyFiles = new FormData();
+ files.forEach(element => {
+ if (element instanceof File) {
+ bodyFiles.append("upload_file", element);
+ fetchPostV2FileUploadMODELCOLLECTIONUUID({
+ //@ts-ignore swagger issue
+ body: bodyFiles,
+ pathParams: { model: "audit-status", collection: "attachments", uuid: resAuditlog.data.uuid as any }
+ });
+ }
+ });
+ setShowNotification(true);
+ setTimeout(() => {
+ setShowNotification(false);
+ }, 3000);
+ setComment("");
+ setError("");
+ setFiles([]);
+ props.refresh?.();
+ setLoading(false);
+ }
+ });
const [files, setFiles] = useState([]);
const [comment, setComment] = useState("");
const [error, setError] = useState("");
const [charCount, setCharCount] = useState(0);
- const [showNotification] = useState(false);
+ const [showNotification, setShowNotification] = useState(false);
const [loading, setLoading] = useState(false);
const [warning, setWarning] = useState("");
- const t = useT();
+
const validFileTypes = [
"application/pdf",
"application/vnd.ms-excel",
@@ -41,6 +76,7 @@ const CommentaryBox = (props: CommentaryBoxProps) => {
];
const maxFileSize = 10 * 1024 * 1024;
const maxFiles = 5;
+
const handleFileChange = (e: React.ChangeEvent) => {
if (e.target.files) {
const file = e.target.files[0];
@@ -61,17 +97,23 @@ const CommentaryBox = (props: CommentaryBoxProps) => {
}
};
const submitComment = () => {
- const body = new FormData();
- body.append("entity_uuid", record?.uuid);
- body.append("status", record?.status);
- body.append("entity", entity as string);
- body.append("comment", comment);
- body.append("type", "comment");
- files.forEach((element: File, index: number) => {
- body.append(`file[${index}]`, element);
- });
+ const body: PostV2AuditStatusENTITYUUIDRequestBody = {
+ status: props.record?.status,
+ comment: comment,
+ type: "comment"
+ };
+
setLoading(true);
+ sendCommentary?.({
+ pathParams: {
+ entity: getRequestPathParam(props.entity!),
+ uuid: props.record?.uuid as string
+ },
+ //@ts-ignore swagger issue
+ body
+ });
};
+
const handleCommentChange = (e: any) => {
setComment(e.target.value);
setCharCount(e.target.value.length);
@@ -81,14 +123,13 @@ const CommentaryBox = (props: CommentaryBoxProps) => {
setWarning("");
}
};
-
return (
- {name?.[0]}
- {lastName?.[0]}
+ {name?.[0] ?? ""}
+ {lastName?.[0] ?? ""}
+
{warning && charCount > 255 &&
{warning}
}
255 ? "text-red" : "text-grey-500"}`}>
@@ -147,6 +189,7 @@ const CommentaryBox = (props: CommentaryBoxProps) => {
+
{error && {error}
}
+ >
+ placeholder
+
{
type?: "success" | "error" | "warning";
@@ -23,12 +20,33 @@ const Notification: FC
= props => {
const t = useT();
const [openNotification, setOpenNotification] = useState(open);
- const textClasses = useMemo(() => (has(TEXT_CLASSES, type) ? TEXT_CLASSES[type] : TEXT_CLASSES.default), [type]);
+ const notificationClasses = useMemo(() => {
+ const baseClasses =
+ "flex items-start rounded-lg font-bold w-full tracking-tighter leading-16 p-4 bg-white shadow-[0_0_5px_0_rgba(0,0,0,0.2)]";
+ switch (type) {
+ case "success":
+ return classNames(baseClasses, "text-bold-body-300 group:text-success-600");
+ case "error":
+ return classNames(baseClasses, "text-bold-body-300 group:text-error-600");
+ case "warning":
+ return classNames(baseClasses, "text-bold-body-300 group:text-tertiary-600");
+ default:
+ return classNames(baseClasses, "text-bold-body-300 group:text-success-600");
+ }
+ }, [type]);
- const notificationClasses = useMemo(
- () => (has(TYPE_CLASSES, type) ? TYPE_CLASSES[type] : TYPE_CLASSES.default),
- [type]
- );
+ const textClasses = useMemo(() => {
+ switch (type) {
+ case "success":
+ return "text-success-600";
+ case "error":
+ return "text-error-600";
+ case "warning":
+ return "text-tertiary-600";
+ default:
+ return "text-success-600";
+ }
+ }, [type]);
useEffect(() => {
setOpenNotification(open);
diff --git a/src/components/elements/Notification/__snapshots__/Notification.stories.storyshot b/src/components/elements/Notification/__snapshots__/Notification.stories.storyshot
index 999be0f8e..9fa9d163b 100644
--- a/src/components/elements/Notification/__snapshots__/Notification.stories.storyshot
+++ b/src/components/elements/Notification/__snapshots__/Notification.stories.storyshot
@@ -8,7 +8,7 @@ exports[`Storyshots Components/Elements/Notification Error 1`] = `
className="fixed top-[86px] right-[1.5vw] z-[1000000] flex w-[28vw] shadow-black"
>
extends Omit
, "on
meta: any;
onTableStateChange?: (state: ServerSideTableState) => void;
onQueryParamChange?: (queryParams: any) => void;
- treeSpeciesShow?: boolean;
}
export function ServerSideTable({
@@ -72,7 +71,6 @@ export function ServerSideTable({
setPage(1);
setPageSize(size);
}}
- treeSpeciesShow={props.treeSpeciesShow}
/>
)}
>
diff --git a/src/components/extensive/Modal/Modal.tsx b/src/components/extensive/Modal/Modal.tsx
index 9d88cef5b..ac759367e 100644
--- a/src/components/extensive/Modal/Modal.tsx
+++ b/src/components/extensive/Modal/Modal.tsx
@@ -7,7 +7,6 @@ import Button, { IButtonProps } from "@/components/elements/Button/Button";
import Text from "@/components/elements/Text/Text";
import Icon, { IconProps } from "../Icon/Icon";
-import { ModalBase } from "./ModalsBases";
export type ModalBaseProps = DetailedHTMLProps, HTMLDivElement>;
export interface ModalProps extends ModalBaseProps {
@@ -18,6 +17,18 @@ export interface ModalProps extends ModalBaseProps {
secondaryButtonProps?: IButtonProps;
}
+export const ModalBase: FC = ({ children, className, ...rest }) => (
+
+ {children}
+
+);
+
const Modal: FC = ({
iconProps,
title,
diff --git a/src/components/extensive/Modal/ModalConfirm.tsx b/src/components/extensive/Modal/ModalConfirm.tsx
index 5095b7edb..102a9d17a 100644
--- a/src/components/extensive/Modal/ModalConfirm.tsx
+++ b/src/components/extensive/Modal/ModalConfirm.tsx
@@ -9,8 +9,7 @@ import TextArea from "@/components/elements/Inputs/textArea/TextArea";
import Text from "@/components/elements/Text/Text";
import { Option } from "@/types/common";
-import { ModalProps } from "./Modal";
-import { ModalBase } from "./ModalsBases";
+import { ModalBase, ModalProps } from "./Modal";
export interface ModalConfirmProps extends ModalProps {
onClose: () => void;
@@ -34,6 +33,7 @@ const ModalConfirm: FC = ({
checkPolygonsSite,
...rest
}) => {
+ const t = useT();
const [data, useData] = useState("");
const [selectedOption, setSelectedOption] = useState(null);
const [showError, setShowError] = useState(false);
@@ -49,7 +49,6 @@ const ModalConfirm: FC = ({
setWarning("");
}
};
- const t = useT();
return (
diff --git a/src/components/extensive/Modal/ModalWithLogo.stories.tsx b/src/components/extensive/Modal/ModalWithLogo.stories.tsx
index 19c78b9f0..a55823498 100644
--- a/src/components/extensive/Modal/ModalWithLogo.stories.tsx
+++ b/src/components/extensive/Modal/ModalWithLogo.stories.tsx
@@ -1,4 +1,5 @@
import { Meta, StoryObj } from "@storybook/react";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { IconNames } from "../Icon/Icon";
import Component, { ModalWithLogoProps as Props } from "./ModalWithLogo";
@@ -11,10 +12,14 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
+const client = new QueryClient();
+
export const Default: Story = {
render: (args: Props) => (
-
+
+
+
),
args: {
diff --git a/src/components/extensive/Modal/__snapshots__/Modal.stories.storyshot b/src/components/extensive/Modal/__snapshots__/Modal.stories.storyshot
index 95897210b..d363bc3c5 100644
--- a/src/components/extensive/Modal/__snapshots__/Modal.stories.storyshot
+++ b/src/components/extensive/Modal/__snapshots__/Modal.stories.storyshot
@@ -5,7 +5,7 @@ exports[`Storyshots Components/Extensive/Modal Default 1`] = `
className="flex items-center justify-center bg-primary-400 p-8"
>
void;
- treeSpeciesShow?: boolean;
variant?: VariantPagination;
}
function Pagination(props: PaginationProps) {
const t = useT();
- return props.treeSpeciesShow ? (
-
- ) : (
+ return (
{props.hasPageSizeSelector ? (
{
const t = useT();
- const [queryParams, setQueryParams] = useState();
+ const [queryParams, setQueryParams] = useState({});
- if (collection && queryParams) {
+ if (collection != null) {
queryParams["filter[collection]"] = collection;
}
@@ -39,13 +39,13 @@ const TreeSpeciesTable = ({ modelName, modelUUID, collection, onFetch, variantTa
? treeSpecies?.data?.reduce((total, item) => total + (typeof item.amount === "number" ? 1 : 0), 0) > 0
: false;
+ const data = treeSpecies?.data?.map(item => ({ ...item, amount: item.amount ?? 0 })) ?? [];
return (
({ ...item, amount: item.amount ?? 0 })) ?? []}
+ data={data}
isLoading={isLoading}
- treeSpeciesShow={true}
onQueryParamChange={setQueryParams}
variant={variantTable}
columns={[
diff --git a/src/constants/entities.ts b/src/constants/entities.ts
new file mode 100644
index 000000000..4980c34c3
--- /dev/null
+++ b/src/constants/entities.ts
@@ -0,0 +1,4 @@
+export const POLYGON = "Polygon";
+export const PROJECT = "Project";
+export const SITE = "Site";
+export const SITE_POLYGON = "SitePolygon";
diff --git a/src/generated/apiComponents.ts b/src/generated/apiComponents.ts
index 7d4722f27..0fcef67e9 100644
--- a/src/generated/apiComponents.ts
+++ b/src/generated/apiComponents.ts
@@ -32021,6 +32021,172 @@ export const usePutV2GeometryUUID = (
);
};
+export type GetV2AuditStatusENTITYUUIDPathParams = {
+ /**
+ * allowed values project/site/site-polygon
+ */
+ entity: string;
+ uuid: string;
+};
+
+export type GetV2AuditStatusENTITYUUIDError = Fetcher.ErrorWrapper;
+
+export type GetV2AuditStatusENTITYUUIDResponse = {
+ id?: string;
+ uuid?: string;
+ entity_name?: string;
+ status?: string;
+ comment?: string;
+ first_name?: string;
+ last_name?: string;
+ type?: string;
+ is_submitted?: boolean;
+ is_active?: boolean;
+ request_removed?: boolean;
+ /**
+ * @format date
+ */
+ date_created?: string;
+ created_by?: string;
+ attachments?: {
+ uuid?: string;
+ url?: string;
+ thumb_url?: string;
+ collection_name?: string;
+ title?: string;
+ file_name?: string;
+ mime_type?: string;
+ size?: number;
+ lat?: number;
+ lng?: number;
+ is_public?: boolean;
+ created_at?: string;
+ }[];
+}[];
+
+export type GetV2AuditStatusENTITYUUIDVariables = {
+ pathParams: GetV2AuditStatusENTITYUUIDPathParams;
+} & ApiContext["fetcherOptions"];
+
+export const fetchGetV2AuditStatusENTITYUUID = (variables: GetV2AuditStatusENTITYUUIDVariables, signal?: AbortSignal) =>
+ apiFetch<
+ GetV2AuditStatusENTITYUUIDResponse,
+ GetV2AuditStatusENTITYUUIDError,
+ undefined,
+ {},
+ {},
+ GetV2AuditStatusENTITYUUIDPathParams
+ >({ url: "/v2/audit-status/{entity}/{uuid}", method: "get", ...variables, signal });
+
+export const useGetV2AuditStatusENTITYUUID = (
+ variables: GetV2AuditStatusENTITYUUIDVariables,
+ options?: Omit<
+ reactQuery.UseQueryOptions,
+ "queryKey" | "queryFn"
+ >
+) => {
+ const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options);
+ return reactQuery.useQuery(
+ queryKeyFn({ path: "/v2/audit-status/{ENTITY}/{UUID}", operationId: "getV2AuditStatusENTITYUUID", variables }),
+ ({ signal }) => fetchGetV2AuditStatusENTITYUUID({ ...fetcherOptions, ...variables }, signal),
+ {
+ ...options,
+ ...queryOptions
+ }
+ );
+};
+
+export type PostV2AuditStatusENTITYUUIDPathParams = {
+ /**
+ * allowed values project/site/site-polygon
+ */
+ entity: string;
+ uuid: string;
+};
+
+export type PostV2AuditStatusENTITYUUIDError = Fetcher.ErrorWrapper;
+
+export type PostV2AuditStatusENTITYUUIDResponse = {
+ id?: string;
+ uuid?: string;
+ entity_name?: string;
+ status?: string;
+ comment?: string;
+ first_name?: string;
+ last_name?: string;
+ type?: string;
+ is_submitted?: boolean;
+ is_active?: boolean;
+ request_removed?: boolean;
+ /**
+ * @format date
+ */
+ date_created?: string;
+ created_by?: string;
+ attachments?: {
+ uuid?: string;
+ url?: string;
+ thumb_url?: string;
+ collection_name?: string;
+ title?: string;
+ file_name?: string;
+ mime_type?: string;
+ size?: number;
+ lat?: number;
+ lng?: number;
+ is_public?: boolean;
+ created_at?: string;
+ }[];
+};
+
+export type PostV2AuditStatusENTITYUUIDRequestBody = {
+ status?: string;
+ comment?: string;
+ type?: string;
+ is_active?: boolean;
+ request_removed?: boolean;
+};
+
+export type PostV2AuditStatusENTITYUUIDVariables = {
+ body?: PostV2AuditStatusENTITYUUIDRequestBody;
+ pathParams: PostV2AuditStatusENTITYUUIDPathParams;
+} & ApiContext["fetcherOptions"];
+
+export const fetchPostV2AuditStatusENTITYUUID = (
+ variables: PostV2AuditStatusENTITYUUIDVariables,
+ signal?: AbortSignal
+) =>
+ apiFetch<
+ PostV2AuditStatusENTITYUUIDResponse,
+ PostV2AuditStatusENTITYUUIDError,
+ PostV2AuditStatusENTITYUUIDRequestBody,
+ {},
+ {},
+ PostV2AuditStatusENTITYUUIDPathParams
+ >({ url: "/v2/audit-status/{entity}/{uuid}", method: "post", ...variables, signal });
+
+export const usePostV2AuditStatusENTITYUUID = (
+ options?: Omit<
+ reactQuery.UseMutationOptions<
+ PostV2AuditStatusENTITYUUIDResponse,
+ PostV2AuditStatusENTITYUUIDError,
+ PostV2AuditStatusENTITYUUIDVariables
+ >,
+ "mutationFn"
+ >
+) => {
+ const { fetcherOptions } = useApiContext();
+ return reactQuery.useMutation<
+ PostV2AuditStatusENTITYUUIDResponse,
+ PostV2AuditStatusENTITYUUIDError,
+ PostV2AuditStatusENTITYUUIDVariables
+ >(
+ (variables: PostV2AuditStatusENTITYUUIDVariables) =>
+ fetchPostV2AuditStatusENTITYUUID({ ...fetcherOptions, ...variables }),
+ options
+ );
+};
+
export type GetV2SitesSitePolygonPathParams = {
/**
* The ID of the site
@@ -33770,6 +33936,142 @@ export const useGetV2TypeEntity = (
);
};
+export type PutV2ENTITYUUIDStatusPathParams = {
+ /**
+ * allowed values project/site/site-polygons
+ */
+ entity: string;
+ uuid: string;
+};
+
+export type PutV2ENTITYUUIDStatusError = Fetcher.ErrorWrapper;
+
+export type PutV2ENTITYUUIDStatusVariables = {
+ pathParams: PutV2ENTITYUUIDStatusPathParams;
+} & ApiContext["fetcherOptions"];
+
+export const fetchPutV2ENTITYUUIDStatus = (variables: PutV2ENTITYUUIDStatusVariables, signal?: AbortSignal) =>
+ apiFetch({
+ url: "/v2/{entity}/{uuid}/status",
+ method: "put",
+ ...variables,
+ signal
+ });
+
+export const usePutV2ENTITYUUIDStatus = (
+ options?: Omit<
+ reactQuery.UseMutationOptions,
+ "mutationFn"
+ >
+) => {
+ const { fetcherOptions } = useApiContext();
+ return reactQuery.useMutation(
+ (variables: PutV2ENTITYUUIDStatusVariables) => fetchPutV2ENTITYUUIDStatus({ ...fetcherOptions, ...variables }),
+ options
+ );
+};
+
+export type GetV2ProjectsUUIDSitePolygonsAllPathParams = {
+ uuid: string;
+};
+
+export type GetV2ProjectsUUIDSitePolygonsAllError = Fetcher.ErrorWrapper;
+
+export type GetV2ProjectsUUIDSitePolygonsAllResponse = {
+ id?: number;
+ uuid?: string;
+ poly_name?: string;
+ status?: string;
+ /**
+ * @format date-time
+ */
+ date_created?: string;
+ created_by?: string;
+}[];
+
+export type GetV2ProjectsUUIDSitePolygonsAllVariables = {
+ pathParams: GetV2ProjectsUUIDSitePolygonsAllPathParams;
+} & ApiContext["fetcherOptions"];
+
+export const fetchGetV2ProjectsUUIDSitePolygonsAll = (
+ variables: GetV2ProjectsUUIDSitePolygonsAllVariables,
+ signal?: AbortSignal
+) =>
+ apiFetch<
+ GetV2ProjectsUUIDSitePolygonsAllResponse,
+ GetV2ProjectsUUIDSitePolygonsAllError,
+ undefined,
+ {},
+ {},
+ GetV2ProjectsUUIDSitePolygonsAllPathParams
+ >({ url: "/v2/projects/{uuid}/site-polygons/all", method: "get", ...variables, signal });
+
+export const useGetV2ProjectsUUIDSitePolygonsAll = (
+ variables: GetV2ProjectsUUIDSitePolygonsAllVariables,
+ options?: Omit<
+ reactQuery.UseQueryOptions,
+ "queryKey" | "queryFn"
+ >
+) => {
+ const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options);
+ return reactQuery.useQuery(
+ queryKeyFn({
+ path: "/v2/projects/{UUID}/site-polygons/all",
+ operationId: "getV2ProjectsUUIDSitePolygonsAll",
+ variables
+ }),
+ ({ signal }) => fetchGetV2ProjectsUUIDSitePolygonsAll({ ...fetcherOptions, ...variables }, signal),
+ {
+ ...options,
+ ...queryOptions
+ }
+ );
+};
+
+export type GetV2SitesSiteCheckApprovePathParams = {
+ site: string;
+};
+
+export type GetV2SitesSiteCheckApproveError = Fetcher.ErrorWrapper;
+
+export type GetV2SitesSiteCheckApproveResponse = {
+ data?: {
+ can_approve?: boolean;
+ };
+};
+
+export type GetV2SitesSiteCheckApproveVariables = {
+ pathParams: GetV2SitesSiteCheckApprovePathParams;
+} & ApiContext["fetcherOptions"];
+
+export const fetchGetV2SitesSiteCheckApprove = (variables: GetV2SitesSiteCheckApproveVariables, signal?: AbortSignal) =>
+ apiFetch<
+ GetV2SitesSiteCheckApproveResponse,
+ GetV2SitesSiteCheckApproveError,
+ undefined,
+ {},
+ {},
+ GetV2SitesSiteCheckApprovePathParams
+ >({ url: "/v2/sites/{site}/check-approve", method: "get", ...variables, signal });
+
+export const useGetV2SitesSiteCheckApprove = (
+ variables: GetV2SitesSiteCheckApproveVariables,
+ options?: Omit<
+ reactQuery.UseQueryOptions,
+ "queryKey" | "queryFn"
+ >
+) => {
+ const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options);
+ return reactQuery.useQuery(
+ queryKeyFn({ path: "/v2/sites/{site}/check-approve", operationId: "getV2SitesSiteCheckApprove", variables }),
+ ({ signal }) => fetchGetV2SitesSiteCheckApprove({ ...fetcherOptions, ...variables }, signal),
+ {
+ ...options,
+ ...queryOptions
+ }
+ );
+};
+
export type QueryOperation =
| {
path: "/v2/tree-species/{entity}/{UUID}";
@@ -34231,6 +34533,11 @@ export type QueryOperation =
operationId: "getV2TerrafundValidationSite";
variables: GetV2TerrafundValidationSiteVariables;
}
+ | {
+ path: "/v2/audit-status/{ENTITY}/{UUID}";
+ operationId: "getV2AuditStatusENTITYUUID";
+ variables: GetV2AuditStatusENTITYUUIDVariables;
+ }
| {
path: "/v2/sites/{site}/polygon";
operationId: "getV2SitesSitePolygon";
@@ -34325,4 +34632,14 @@ export type QueryOperation =
path: "/v2/type-entity";
operationId: "getV2TypeEntity";
variables: GetV2TypeEntityVariables;
+ }
+ | {
+ path: "/v2/projects/{UUID}/site-polygons/all";
+ operationId: "getV2ProjectsUUIDSitePolygonsAll";
+ variables: GetV2ProjectsUUIDSitePolygonsAllVariables;
+ }
+ | {
+ path: "/v2/sites/{site}/check-approve";
+ operationId: "getV2SitesSiteCheckApprove";
+ variables: GetV2SitesSiteCheckApproveVariables;
};
diff --git a/src/generated/apiSchemas.ts b/src/generated/apiSchemas.ts
index a888f2621..3e05005c8 100644
--- a/src/generated/apiSchemas.ts
+++ b/src/generated/apiSchemas.ts
@@ -23004,6 +23004,47 @@ export type GeometryPost = {
};
};
+export type AuditStatusCreateRequest = {
+ status?: string;
+ comment?: string;
+ type?: string;
+ is_active?: boolean;
+ request_removed?: boolean;
+};
+
+export type AuditStatusResponse = {
+ id?: string;
+ uuid?: string;
+ entity_name?: string;
+ status?: string;
+ comment?: string;
+ first_name?: string;
+ last_name?: string;
+ type?: string;
+ is_submitted?: boolean;
+ is_active?: boolean;
+ request_removed?: boolean;
+ /**
+ * @format date
+ */
+ date_created?: string;
+ created_by?: string;
+ attachments?: {
+ uuid?: string;
+ url?: string;
+ thumb_url?: string;
+ collection_name?: string;
+ title?: string;
+ file_name?: string;
+ mime_type?: string;
+ size?: number;
+ lat?: number;
+ lng?: number;
+ is_public?: boolean;
+ created_at?: string;
+ }[];
+};
+
export type V2TerrafundCriteriaData = {
/**
* The ID of the polygon
@@ -23447,3 +23488,27 @@ export type EntityTypeResponse = {
*/
bbox?: number[];
};
+
+export type AuditStatusUpdateRequest = {
+ type?: string;
+ comment?: string;
+ status?: string;
+ is_active?: boolean;
+ request_removed?: boolean;
+};
+
+export type SitePolygonResource = {
+ id?: number;
+ uuid?: string;
+ poly_name?: string;
+ status?: string;
+ /**
+ * @format date-time
+ */
+ date_created?: string;
+ created_by?: string;
+};
+
+export type SiteCheckApproveResponse = {
+ can_approve?: boolean;
+};
diff --git a/src/hooks/AuditStatus/useAuditLogActions.ts b/src/hooks/AuditStatus/useAuditLogActions.ts
new file mode 100644
index 000000000..fc8aee96f
--- /dev/null
+++ b/src/hooks/AuditStatus/useAuditLogActions.ts
@@ -0,0 +1,176 @@
+import { useEffect, useState } from "react";
+
+import { AuditLogButtonStates } from "@/admin/components/ResourceTabs/AuditLogTab/constants/enum";
+import { AuditLogEntity } from "@/admin/components/ResourceTabs/AuditLogTab/constants/types";
+import { POLYGON, PROJECT, SITE } from "@/constants/entities";
+import {
+ fetchGetV2SitesSiteCheckApprove,
+ fetchPostV2TerrafundValidationPolygon,
+ fetchPutV2ENTITYUUIDStatus,
+ GetV2AuditStatusENTITYUUIDResponse,
+ useGetV2AuditStatusENTITYUUID
+} from "@/generated/apiComponents";
+import {
+ getValueForStatusPolygon,
+ getValueForStatusProject,
+ getValueForStatusSite,
+ polygonProgressBarStatusLabels,
+ projectStatusLabels,
+ siteProgressBarStatusLabels
+} from "@/utils/statusUtils";
+
+import useLoadEntityList from "./useLoadEntityList";
+
+const ESTIMATED_AREA_CRITERIA_ID = 12;
+
+const ReverseButtonStates2: { [key: number]: string } = {
+ 0: "project",
+ 1: "site",
+ 2: "site-polygon"
+};
+
+const statusActionsMap = {
+ [AuditLogButtonStates.PROJECT as number]: {
+ mutateEntity: fetchPutV2ENTITYUUIDStatus,
+ valuesForStatus: getValueForStatusProject,
+ statusLabels: projectStatusLabels,
+ entityType: PROJECT
+ },
+ [AuditLogButtonStates.SITE as number]: {
+ mutateEntity: fetchPutV2ENTITYUUIDStatus,
+ valuesForStatus: getValueForStatusSite,
+ statusLabels: siteProgressBarStatusLabels,
+ entityType: SITE
+ },
+ [AuditLogButtonStates.POLYGON as number]: {
+ mutateEntity: fetchPutV2ENTITYUUIDStatus,
+ valuesForStatus: getValueForStatusPolygon,
+ statusLabels: polygonProgressBarStatusLabels,
+ entityType: POLYGON
+ }
+};
+
+interface AuditLogActionsResponse {
+ mutateEntity: any;
+ valuesForStatus: any;
+ statusLabels: any;
+ entityType: AuditLogEntity;
+ loadEntityList: () => void;
+ entityListItem: any;
+ selected: any;
+ setSelected: any;
+ checkPolygonsSite: boolean | undefined;
+ auditLogData: { data: GetV2AuditStatusENTITYUUIDResponse } | undefined;
+ refetch: () => void;
+ isLoading: boolean;
+}
+
+const useAuditLogActions = ({
+ record,
+ buttonToogle,
+ entityLevel
+}: {
+ record: any;
+ buttonToogle: number;
+ entityLevel: string;
+}): AuditLogActionsResponse => {
+ const { mutateEntity, valuesForStatus, statusLabels, entityType } = statusActionsMap[buttonToogle];
+ const isProject = buttonToogle === AuditLogButtonStates.PROJECT;
+ const isSite = buttonToogle === AuditLogButtonStates.SITE;
+ const isPolygon = buttonToogle === AuditLogButtonStates.POLYGON;
+ const isSiteProject = entityLevel === PROJECT;
+ const [checkPolygons, setCheckPolygons] = useState(undefined);
+ const [criteriaValidation, setCriteriaValidation] = useState();
+ const { entityListItem, selected, setSelected, loadEntityList } = useLoadEntityList({
+ entityUuid: record?.uuid,
+ entityType: entityType as AuditLogEntity,
+ buttonToogle,
+ entityLevel
+ });
+
+ useEffect(() => {
+ const fetchCheckPolygons = async () => {
+ if (entityType === "Site" && record?.uuid && isSite) {
+ const result = await fetchGetV2SitesSiteCheckApprove({
+ pathParams: { site: isSiteProject ? selected?.uuid : record.uuid }
+ });
+ setCheckPolygons(result.data?.can_approve);
+ }
+ };
+
+ const fetchCriteriaValidation = async () => {
+ if (selected?.poly_id && isPolygon) {
+ const criteriaData = await fetchPostV2TerrafundValidationPolygon({
+ queryParams: {
+ uuid: selected?.poly_id as string
+ }
+ });
+ setCriteriaValidation(criteriaData);
+ }
+ };
+
+ fetchCheckPolygons();
+ fetchCriteriaValidation();
+ }, [entityType, record, selected]);
+
+ const isValidCriteriaData = (criteriaData: any) => {
+ if (!criteriaData?.criteria_list?.length) {
+ return true;
+ }
+ return criteriaData.criteria_list.some(
+ (criteria: any) => criteria.criteria_id !== ESTIMATED_AREA_CRITERIA_ID && criteria.valid !== 1
+ );
+ };
+
+ const entityHandlers = (() => {
+ if (isSiteProject) {
+ return {
+ selectedEntityItem: isProject ? record : selected,
+ loadToEntity: !isProject ? loadEntityList : () => {},
+ ListItemToEntity: !isProject ? entityListItem : [],
+ setSelectedToEntity: !isProject ? setSelected : null,
+ checkPolygons: isSite ? checkPolygons : isPolygon ? isValidCriteriaData(criteriaValidation) : false
+ };
+ } else {
+ return {
+ selectedEntityItem: isProject ? record.project : isSite ? record : selected,
+ loadToEntity: !isProject && !isSite ? loadEntityList : () => {},
+ ListItemToEntity: !isProject && !isSite ? entityListItem : [],
+ setSelectedToEntity: !isProject && !isSite ? setSelected : null,
+ checkPolygons: isSite ? checkPolygons : isPolygon ? isValidCriteriaData(criteriaValidation) : false
+ };
+ }
+ })();
+
+ const {
+ data: auditLogData,
+ refetch,
+ isLoading
+ } = useGetV2AuditStatusENTITYUUID<{ data: GetV2AuditStatusENTITYUUIDResponse }>({
+ pathParams: {
+ entity: ReverseButtonStates2[buttonToogle],
+ uuid: entityHandlers.selectedEntityItem?.uuid
+ }
+ });
+
+ useEffect(() => {
+ refetch();
+ }, [buttonToogle, record, entityListItem, selected]);
+
+ return {
+ mutateEntity,
+ valuesForStatus,
+ statusLabels,
+ entityType: entityType as AuditLogEntity,
+ loadEntityList: entityHandlers.loadToEntity,
+ entityListItem: entityHandlers.ListItemToEntity,
+ selected: entityHandlers.selectedEntityItem,
+ setSelected: entityHandlers.setSelectedToEntity,
+ checkPolygonsSite: entityHandlers.checkPolygons,
+ auditLogData,
+ refetch,
+ isLoading
+ };
+};
+
+export default useAuditLogActions;
diff --git a/src/hooks/AuditStatus/useLoadEntityList.ts b/src/hooks/AuditStatus/useLoadEntityList.ts
new file mode 100644
index 000000000..d0126dabf
--- /dev/null
+++ b/src/hooks/AuditStatus/useLoadEntityList.ts
@@ -0,0 +1,115 @@
+import { useEffect, useRef, useState } from "react";
+
+import { AuditLogEntity } from "@/admin/components/ResourceTabs/AuditLogTab/constants/types";
+import { POLYGON, PROJECT, SITE } from "@/constants/entities";
+import {
+ fetchGetV2ProjectsUUIDSitePolygonsAll,
+ fetchGetV2ProjectsUUIDSites,
+ fetchGetV2SitesSitePolygon
+} from "@/generated/apiComponents";
+
+export interface SelectedItem {
+ title?: string | undefined;
+ uuid?: string | undefined;
+ value?: string | undefined;
+ meta?: string | undefined;
+ status?: string | undefined;
+ poly_id?: string | undefined;
+}
+
+interface UseLoadEntityListParams {
+ entityUuid: string;
+ entityType: AuditLogEntity;
+ buttonToogle?: number;
+ entityLevel?: string;
+}
+
+export interface EntityListItem {
+ poly_name?: string | undefined;
+ name?: string | undefined;
+ uuid?: string | undefined;
+ value?: string | undefined;
+ meta?: string | undefined;
+ status?: string | undefined;
+ poly_id?: string | undefined;
+}
+
+const useLoadEntityList = ({ entityUuid, entityType, buttonToogle, entityLevel }: UseLoadEntityListParams) => {
+ const [selected, setSelected] = useState(null);
+ const [entityListItem, setEntityListItem] = useState([]);
+ const isFirstLoad = useRef(true);
+
+ const getNameProperty = (entityType: string): keyof EntityListItem => {
+ switch (entityType) {
+ case POLYGON:
+ return "poly_name";
+ case SITE:
+ return "name";
+ default:
+ return "name";
+ }
+ };
+
+ const unnamedTitleAndSort = (list: EntityListItem[], nameProperty: keyof EntityListItem) => {
+ const unnamedItems = list?.map((item: EntityListItem) => {
+ if (!item[nameProperty]) {
+ return {
+ ...item,
+ [nameProperty]: nameProperty === "poly_name" ? "Unnamed Polygon" : "Unnamed Site"
+ };
+ }
+ return item;
+ });
+
+ return unnamedItems?.sort((a, b) => {
+ const nameA = a[nameProperty];
+ const nameB = b[nameProperty];
+ return nameA && nameB ? nameA.localeCompare(nameB) : 0;
+ });
+ };
+
+ const loadEntityList = async () => {
+ const isSiteProjectLevel = entityLevel === PROJECT;
+ const fetchToProject = entityType == SITE ? fetchGetV2ProjectsUUIDSites : fetchGetV2ProjectsUUIDSitePolygonsAll;
+ const fetchAction = isSiteProjectLevel ? fetchToProject : fetchGetV2SitesSitePolygon;
+ const params = isSiteProjectLevel ? { uuid: entityUuid } : { site: entityUuid };
+ const res = await fetchAction({
+ // @ts-ignore
+ pathParams: params
+ });
+ const _entityList = (res as { data: EntityListItem[] })?.data ?? (res as EntityListItem[]);
+ const nameProperty = getNameProperty(entityType);
+ const transformEntityListItem = (item: EntityListItem) => {
+ return {
+ title: item?.[nameProperty],
+ uuid: item?.uuid,
+ value: item?.uuid,
+ meta: item?.status,
+ status: item?.status,
+ poly_id: item?.poly_id
+ };
+ };
+ const _list = unnamedTitleAndSort(_entityList, nameProperty);
+ setEntityListItem(_list?.map((item: EntityListItem) => transformEntityListItem(item)));
+ if (_list?.length > 0) {
+ if (isFirstLoad.current) {
+ isFirstLoad.current = false;
+ setSelected(transformEntityListItem(_list[0]));
+ } else {
+ const currentSelected = _entityList?.find(item => item?.uuid === selected?.uuid);
+ setSelected(transformEntityListItem(currentSelected as EntityListItem));
+ }
+ } else {
+ setSelected(null);
+ }
+ };
+ useEffect(() => {
+ setSelected(null);
+ setEntityListItem([]);
+ isFirstLoad.current = true;
+ }, [entityType, buttonToogle]);
+
+ return { entityListItem, selected, setSelected, loadEntityList };
+};
+
+export default useLoadEntityList;
diff --git a/src/hooks/useGetCustomFormSteps/__snapshots__/useGetCustomFormSteps.stories.storyshot b/src/hooks/useGetCustomFormSteps/__snapshots__/useGetCustomFormSteps.stories.storyshot
index ac78c56c0..c503535ee 100644
--- a/src/hooks/useGetCustomFormSteps/__snapshots__/useGetCustomFormSteps.stories.storyshot
+++ b/src/hooks/useGetCustomFormSteps/__snapshots__/useGetCustomFormSteps.stories.storyshot
@@ -419,7 +419,7 @@ exports[`Storyshots Components/Extensive/Form/Wizard With Get Form Step Hook 1`]
className="flex items-center gap-2"
>
({
pathParams: {
uuid: modelUUID,
@@ -11,31 +10,21 @@ export function useProcessRecordData(modelUUID: string, modelName: string, input
}
});
- const ProcesssRecordData = useMemo(() => {
- const viewDataTable = record?.data?.form?.form_sections.map((formSection: any) =>
- formSection.form_questions
- .map((formQuestion: any) => formQuestion.uuid)
- .map((formQuestionUUID: any) => record?.data?.answers?.[formQuestionUUID])
- );
+ return useMemo(() => {
+ if (record?.data?.form == null) return false;
- for (let sectionIndex in record?.data?.form?.form_sections) {
- for (let questionIndex in record?.data?.form?.form_sections[sectionIndex].form_questions) {
- const question = record?.data?.form?.form_sections[sectionIndex].form_questions[questionIndex];
- if (question.children) {
- for (let child of question.children) {
- if (child.input_type === inputType) {
- setShow(viewDataTable?.[sectionIndex]?.[questionIndex]);
- }
+ for (const section of record.data.form.form_sections) {
+ for (const question of section.form_questions) {
+ for (const child of question.children ?? []) {
+ if (child.input_type == inputType) {
+ // Only hide the data if the answer is an explicit false in order to not break the UI
+ // for reports that are older than the current version of the form.
+ return record.data.answers![child.parent_id] !== false;
}
- } else {
- setShow(true);
}
}
}
- return show;
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [record, inputType, modelUUID, modelName, show]);
-
- return ProcesssRecordData;
+ return true;
+ }, [record, inputType, modelUUID, modelName]);
}
diff --git a/src/pages/reports/site-report/[uuid].page.tsx b/src/pages/reports/site-report/[uuid].page.tsx
index 01e507adb..e1ee18eb0 100644
--- a/src/pages/reports/site-report/[uuid].page.tsx
+++ b/src/pages/reports/site-report/[uuid].page.tsx
@@ -161,9 +161,19 @@ const SiteReportDetailPage = () => {
{siteReport.public_narrative}
-
+
+
+
diff --git a/src/utils/statusUtils.ts b/src/utils/statusUtils.ts
new file mode 100644
index 000000000..2cea4868a
--- /dev/null
+++ b/src/utils/statusUtils.ts
@@ -0,0 +1,77 @@
+import { convertDateFormat } from "@/admin/apiProvider/utils/entryFormat";
+import { AuditStatusResponse } from "@/generated/apiSchemas";
+
+export function getValueForStatusPolygon(status: string): number {
+ switch (status) {
+ case "draft":
+ return 0;
+ case "submitted":
+ return 34;
+ case "needs-more-information":
+ return 67;
+ case "approved":
+ return 100;
+ default:
+ return 0;
+ }
+}
+
+export function getValueForStatusSite(status: string): number {
+ switch (status) {
+ case "draft":
+ return 0;
+ case "awaiting-approval":
+ return 25;
+ case "needs-more-information":
+ return 50;
+ case "planting-in-progress":
+ return 75;
+ case "approved":
+ return 100;
+ default:
+ return 0;
+ }
+}
+
+export function getValueForStatusProject(status: string): number {
+ switch (status) {
+ case "started":
+ return 0;
+ case "awaiting-approval":
+ return 34;
+ case "needs-more-information":
+ return 67;
+ case "approved":
+ return 100;
+ default:
+ return 0;
+ }
+}
+
+export const polygonProgressBarStatusLabels = [
+ { id: "1", label: "Draft" },
+ { id: "2", label: "Submitted" },
+ { id: "3", label: "Needs More Information" },
+ { id: "4", label: "Approved" }
+];
+
+export const siteProgressBarStatusLabels = [
+ { id: "1", label: "Draft" },
+ { id: "2", label: "Awaiting Approval" },
+ { id: "3", label: "Needs More Information" },
+ { id: "4", label: "Planting in Progress" },
+ { id: "5", label: "Approved" }
+];
+
+export const projectStatusLabels = [
+ { id: "1", label: "Draft" },
+ { id: "2", label: "Awaiting Approval" },
+ { id: "3", label: "Needs More Information" },
+ { id: "4", label: "Approved" }
+];
+
+export const recentRequestData = (recentRequest: AuditStatusResponse | undefined) => {
+ if (!recentRequest) return "";
+ return `From ${recentRequest.first_name ?? ""} ${recentRequest.last_name ?? ""} on
+ ${convertDateFormat(recentRequest.date_created) ?? ""}`;
+};