Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion submit-api/src/submit_api/services/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ def has_access_to_package(package_id):
account_user: AccountUserModel = user.account_user
user_roles: list[UserRoleModel] = account_user.roles

sufficient_roles = {RoleEnum.PROJECT_ADMIN.value, RoleEnum.SUBMISSION_ADMIN.value}
sufficient_roles = {
RoleEnum.ACCOUNT_PRIMARY_ADMIN.value,
RoleEnum.PROJECT_ADMIN.value,
RoleEnum.SUBMISSION_ADMIN.value,
}

for user_role in user_roles:
if user_role.role.role_name in sufficient_roles:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { useCallback, useEffect, useMemo } from "react";
import { Box, Grid, Typography } from "@mui/material";
import { BCDesignTokens, EAOColors } from "epic.theme";
import { Navigate, useParams } from "@tanstack/react-router";
import { notify } from "@/components/Shared/Snackbar/snackbarStore";
import { SUBMISSION_TYPE } from "@/models/Submission";
import { ControlledFileUpload } from "@/components/Shared/ControlledFormFields/ControlledFileUpload";
import { useQueryClient } from "@tanstack/react-query";
import { SubmissionItem } from "@/models/SubmissionItem";
import DocumentTable from "@/components/App/DocumentUpload/DocumentTable";
import { QUERY_KEY } from "@/hooks/api/constants";
import { getAccountProjectQueryOptions } from "@/hooks/api/useProjects";
import { AccountProject } from "@/models/Project";
import { camelCase } from "lodash";
import { useFileStore } from "@/store/fileStore";
import { BarBlueTitle } from "@/components/Shared/Text/BarTitle";
import { getSubmissionFolderName } from "@/components/Shared/Table/utils";

export interface UploadSectionConfig {
name: string;
label: string;
folder: string;
maxFiles?: number;
maxFilesErrorMessage?: string;
description?: string;
acceptedFileTypes?: string;
}

interface GenericDocumentUploadSectionProps {
sections: UploadSectionConfig[];
title?: string;
}

export const GenericDocumentUploadSection: React.FC<
GenericDocumentUploadSectionProps
> = ({ sections, title = "Document(s) Upload" }) => {
const { submissionId: submissionItemId, projectId } = useParams({
from: "/proponent/_proponentLayout/projects/$projectId/_projectLayout/submission-packages/$submissionPackageId/_submissionLayout/submissions/$submissionId",
});

const queryClient = useQueryClient();
const submissionItem = queryClient.getQueryData<SubmissionItem>([
QUERY_KEY.SUBMISSION_ITEM,
Number(submissionItemId),
]);

const getDocumentSubmissions = useCallback(() => {
if (!submissionItem) return [];
return submissionItem.submissions.filter(
(submission) => submission.type === SUBMISSION_TYPE.DOCUMENT,
);
}, [submissionItem]);

const accountProject = queryClient.getQueryData<AccountProject>(
getAccountProjectQueryOptions(Number(projectId)).queryKey,
);

const { reset, files, addPendingFile, pendingFiles, initializeFiles } =
useFileStore();

useEffect(() => {
return () => {
reset();
};
}, [reset]);

useEffect(() => {
initializeFiles(getDocumentSubmissions());
}, [submissionItem, getDocumentSubmissions, initializeFiles]);

const handleOnDrop = (acceptedFiles: File[], folder: string) => {
acceptedFiles.forEach((file) => {
addPendingFile(file, folder);
});
};

const projectName = useMemo(
() => camelCase(accountProject?.project.name ?? ""),
[accountProject],
);

if (!submissionItemId) {
notify.error("Failed to load submission item");
return <Navigate to="/error" />;
}

if (!accountProject) {
notify.error("Failed to load project");
return null;
}

return (
<Grid container spacing={2}>
<Grid item xs={12}>
<BarBlueTitle title={title} bold={false} />
</Grid>
{sections.map((section) => {
const sectionDocuments = files?.filter(
(submission) =>
submission.submitted_document?.folder === section.folder,
);
const pendingSectionDocuments = pendingFiles.filter(
(document) => document.folder === section.folder,
);

return (
<Grid item xs={12} key={section.name}>
<Box sx={{ flexDirection: "column", display: "flex" }}>
<Typography
variant="body1"
color={BCDesignTokens.typographyColorPrimary}
fontWeight={700}
mt={BCDesignTokens.layoutMarginMedium}
>
Upload {section.label}
</Typography>
{section.description ? (
<Typography
variant="body2"
sx={{
color: BCDesignTokens.typographyColorPlaceholder,
}}
>
{section.description}
</Typography>
) : (
<>
<Typography
variant="body2"
sx={{
color: BCDesignTokens.typographyColorPlaceholder,
}}
>
Must be unlocked PDF document (i.e., not password
protected).
</Typography>
<Typography
variant="body2"
sx={{
color: BCDesignTokens.typographyColorPlaceholder,
}}
>
Any proposed changes must be in tracked changes.
</Typography>
</>
)}
</Box>
<ControlledFileUpload
name={section.name}
height={"13.125rem"}
onDrop={(acceptedFiles) =>
handleOnDrop(acceptedFiles, section.folder)
}
maxFiles={section.maxFiles}
maxFilesErrorMessage={section.maxFilesErrorMessage}
/>
<Typography
variant="body2"
sx={{
color: EAOColors.ProponentDark,
}}
>
Accepted file types:{" "}
{section.acceptedFileTypes || "pdf, doc, docx, xlsx"}. Max. file
size: 500 MB.
</Typography>

<Box my={BCDesignTokens.layoutMarginLarge}>
<DocumentTable
header={section.label}
documents={sectionDocuments}
pendingDocuments={pendingSectionDocuments}
folder={getSubmissionFolderName({
projectName: projectName,
sectionName: section.folder,
})}
formFieldName={section.name}
/>
</Box>
</Grid>
);
})}
</Grid>
);
};
76 changes: 42 additions & 34 deletions submit-web/src/components/App/NewManagementPlan/Conditions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,42 +130,50 @@ export const Conditions = () => {
</Typography>
</Grid>
<Grid item xs md={6} lg={4}>
<TextField
data-testid="main-condition"
select
fullWidth
sx={{ marginBottom: "10px" }}
onChange={(e) => {
setMainCondition(
conditions?.find((c) => c.plan_name === e.target.value) || null,
);
if (errorText) {
setErrorText(null);
}
}}
value={mainCondition?.plan_name || ""}
error={!mainCondition && Boolean(errorText)}
>
{conditions
?.filter(
(condition) =>
condition.condition_number !== null && // Ensure condition_number is not null
!supportingConditions.includes(condition.condition_number),
)
.map((condition) => {
const conditionLabel = `Condition ${condition.condition_number} - ${condition.plan_name}`;

return (
<MenuItem
key={condition.plan_name || ""}
value={condition.plan_name || ""}
>
{conditionLabel}
</MenuItem>
{isLoading && !conditions ? (
<Box width={300}>
<Skeleton animation="wave" />
</Box>
) : (
<TextField
data-testid="main-condition"
select
fullWidth
sx={{ marginBottom: "10px" }}
onChange={(e) => {
setMainCondition(
conditions?.find((c) => c.plan_name === e.target.value) ||
null,
);
})}
</TextField>
if (errorText) {
setErrorText(null);
}
}}
value={mainCondition?.plan_name || ""}
error={!mainCondition && Boolean(errorText)}
>
{conditions
?.filter(
(condition) =>
condition.condition_number !== null &&
!supportingConditions.includes(condition.condition_number),
)
.map((condition) => {
const conditionLabel = `Condition ${condition.condition_number} - ${condition.plan_name}`;

return (
<MenuItem
key={condition.plan_name || ""}
value={condition.plan_name || ""}
>
{conditionLabel}
</MenuItem>
);
})}
</TextField>
)}
</Grid>

{errorText && (
<Grid item xs={12} mb={"15px"}>
<Typography color="error" variant="body2">
Expand Down

This file was deleted.

Loading