Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6175713
feat: CE-2133 Federal Laws (#1418)
nayr974 Feb 10, 2026
2fb33dc
feat: Update business fields (#1414)
afwilcox Feb 10, 2026
26bee36
feat: CE-1946 udpate persons fields (#1419)
mikevespi Feb 11, 2026
3cfd6b0
Handle attachment uploads with duplicate names
dk-bcps Feb 11, 2026
bbb3e32
chore(deps): update dependency lodash to v4.17.23 [security] (#1404)
renovate[bot] Feb 17, 2026
862275e
chore(deps): update dependency fast-xml-parser to v5.3.4 [security] (…
renovate[bot] Feb 17, 2026
740f4b4
chore(deps): update dependency @apollo/server to v4.13.0 [security] (…
renovate[bot] Feb 17, 2026
9b9b267
feat: Legislation Management (#1425)
afwilcox Feb 17, 2026
1faf9a4
chore(deps): update dependency axios to v1.13.5 [security] (#1421)
renovate[bot] Feb 17, 2026
720e237
feat: CE-2125 links to legislation (#1424)
mikevespi Feb 17, 2026
0d15228
chore(deps): replace dependency @tsconfig/node16 with @tsconfig/node1…
renovate[bot] Feb 17, 2026
a7c55ad
fix: CE-2140 Clicking on cancel for outcome attachments when there ar…
dk-bcps Feb 18, 2026
f2ccdd8
chore: CE-2125 fix filter Acts only on admin page (#1433)
mikevespi Feb 18, 2026
3985762
fix: CE-2192 Button Colours (#1435)
nayr974 Feb 18, 2026
6bbadf8
chore(deps): update dependency fast-xml-parser to v5.3.6 [security] (…
renovate[bot] Feb 18, 2026
9bb915a
Merge branch 'release/2.17' into featCE-2144-Handle-attachment-upload…
afwilcox Feb 19, 2026
628416a
Merge branch 'release/2.18' into featCE-2144-Handle-attachment-upload…
afwilcox Feb 19, 2026
89bd866
Handle naming for complaint and outcome attachments
dk-bcps Feb 20, 2026
1d3896d
Merge branch 'release/2.18' into featCE-2144-Handle-attachment-upload…
afwilcox Feb 20, 2026
84004e8
Decode attachment names to handle uploads with duplicate names
dk-bcps Feb 20, 2026
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
26 changes: 26 additions & 0 deletions frontend/src/app/common/methods.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,32 @@ export const injectIdentifierToFilename = (
return `${fileNameWithoutExtension}_${identifier}_${attachmentType}${fileExtension}`;
};

export const removeIdentifierFromFilename = (
formattedFilename: string,
identifier: string,
attachmentType: AttachmentEnum,
): string => {
const suffix = `_${identifier}_${attachmentType}`;
const lastDotIndex = formattedFilename.lastIndexOf(".");

if (lastDotIndex === -1) {
if (formattedFilename.endsWith(suffix)) {
return formattedFilename.slice(0, -suffix.length);
}
return formattedFilename;
}

const fileNameWithoutExtension = formattedFilename.substring(0, lastDotIndex);
const fileExtension = formattedFilename.substring(lastDotIndex);

if (fileNameWithoutExtension.endsWith(suffix)) {
const cleanedName = fileNameWithoutExtension.slice(0, -suffix.length);
return `${cleanedName}${fileExtension}`;
}

return formattedFilename;
};

export const isImage = (filename: string): boolean => {
return ["jpg", "jpeg", "png", "gif", "bmp", "webp", "abif", "svg"].includes(getFileExtension(filename));
};
Expand Down
46 changes: 44 additions & 2 deletions frontend/src/app/components/common/attachments-carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { getAttachments } from "@store/reducers/attachments";
import { AttachmentSlide } from "./attachment-slide";
import { AttachmentUpload } from "./attachment-upload";
import { COMSObject } from "@apptypes/coms/object";
import { selectMaxFileSize } from "@store/reducers/app";
import { openModal, selectMaxFileSize } from "@store/reducers/app";
import { v4 as uuidv4 } from "uuid";
import { getThumbnailDataURL, isImage } from "@common/methods";
import { getThumbnailDataURL, isImage, removeIdentifierFromFilename } from "@common/methods";
import AttachmentEnum from "@constants/attachment-enum";
import { getDisplayFilename } from "@/app/common/attachment-utils";
import { CANCEL_CONFIRM_FILE_UPDATE } from "@/app/types/modal/modal-types";

type Props = {
attachmentType: AttachmentEnum;
Expand Down Expand Up @@ -118,8 +120,48 @@ export const Attachments: FC<Props> = ({
return copy;
}

const confirmFileUpdate = async (newFiles: FileList) => {
let exisingFileNames: string[] = [];

if (attachmentType === AttachmentEnum.TASK_ATTACHMENT) {
exisingFileNames = slides.map((attachment) => {
return getDisplayFilename(attachment.name);
});
} else {
exisingFileNames = slides.map((attachment) => {
return removeIdentifierFromFilename(decodeURIComponent(attachment.name), identifier ?? "", attachmentType);
});
}

const exisingFileNamesSet = new Set(exisingFileNames);
const newFileNamesArray = Array.from(newFiles).map((file) => file.name);
const conflitingFileNames = newFileNamesArray.filter((item) => exisingFileNamesSet.has(item));
if (conflitingFileNames.length === 0) {
await stageFiles(newFiles);
} else {
document.body.click();
dispatch(
openModal({
modalSize: "md",
modalType: CANCEL_CONFIRM_FILE_UPDATE,
data: {
title: "File already exists",
fileNames: conflitingFileNames,
onUpdate: async () => {
await stageFiles(newFiles);
},
},
}),
);
}
};

// when a user selects files (via the file browser that pops up when clicking the upload slide) then add them to the carousel
const onFileSelect = async (newFiles: FileList) => {
await confirmFileUpdate(newFiles);
};

const stageFiles = async (newFiles: FileList) => {
const selectedFilesArray = Array.from(newFiles);
let newSlides: COMSObject[] = [];
for (let selectedFile of selectedFilesArray) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { FC } from "react";
import { Modal, Button } from "react-bootstrap";
import { useAppSelector } from "@hooks/hooks";
import { selectModalData } from "@store/reducers/app";

type CancelConfirmFileUpdateModalProps = {
close: () => void;
onUpdate: () => void;
};

type ModalData = {
title: string;
fileNames: string[];
};

export const CancelConfirmFileUpdateModal: FC<CancelConfirmFileUpdateModalProps> = ({ close, onUpdate }) => {
const modalData = useAppSelector(selectModalData) as ModalData;
const { title, fileNames } = modalData;

if (fileNames?.length === 0) {
return null;
}

const isSingleFile = fileNames.length === 1;

const handleSubmit = () => {
onUpdate();
close();
};

const handleCancel = () => {
close();
};

return (
<>
{title && (
<Modal.Header closeButton>
<Modal.Title>{title}</Modal.Title>
</Modal.Header>
)}

<Modal.Body>
<p>
{isSingleFile ? (
<>
An attachment with the name <strong>{fileNames[0]}</strong> already exists. If this is the latest version
of that document, please click <strong>“Update document”</strong>. If this is intended to be a new,
separate document, please click <strong>“Cancel”</strong> and rename the file before uploading it.
</>
) : (
<>
<span>Attachments with the following names already exist.</span>
<ul className="mt-3 list-unstyled">
{fileNames.map((fileName) => (
<li
key={fileName}
className="py-1 px-4"
>
{fileName}
</li>
))}
</ul>
<span>
If this is the latest version of the documents, please click <strong>“Update document”</strong>. If they
are intended to be new, separate documents, please click <strong>“Cancel”</strong> and rename the files
before uploading them.
</span>
</>
)}
</p>
</Modal.Body>

<Modal.Footer>
<Button
variant="outline-primary"
onClick={handleCancel}
>
Cancel
</Button>
<Button onClick={handleSubmit}>Update document</Button>
</Modal.Footer>
</>
);
};
3 changes: 3 additions & 0 deletions frontend/src/app/components/modal/model-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ADD_PARTY,
REMOVE_ACTIVITY_FROM_CASE,
REMOVE_PARTY,
CANCEL_CONFIRM_FILE_UPDATE,
} from "@apptypes/modal/modal-types";

import {
Expand All @@ -39,6 +40,7 @@ import { AddComplaintToCaseModal } from "./instances/add-complaint-to-case";
import { CreateAddCaseModal } from "@/app/components/modal/instances/create-add-case";
import { RemoveActivityFromCaseModal } from "@/app/components/modal/instances/remove-activity-from-case";
import { AddPartyModal } from "@/app/components/modal/instances/add-party";
import { CancelConfirmFileUpdateModal } from "./instances/cancel-confirm-file-update-modal";

export const MODAL_COMPONENTS: { [key: string]: React.ComponentType<any> } = {
[Sample]: SampleModal,
Expand All @@ -59,4 +61,5 @@ export const MODAL_COMPONENTS: { [key: string]: React.ComponentType<any> } = {
[REMOVE_ACTIVITY_FROM_CASE]: RemoveActivityFromCaseModal,
[ADD_PARTY]: AddPartyModal,
[REMOVE_PARTY]: RemovePartyModal,
[CANCEL_CONFIRM_FILE_UPDATE]: CancelConfirmFileUpdateModal,
};
1 change: 1 addition & 0 deletions frontend/src/app/types/modal/modal-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const CREATE_ADD_CASE = "CREATE_ADD_CASE";
export const REMOVE_ACTIVITY_FROM_CASE = "REMOVE_ACTIVITY_FROM_CASE";
export const ADD_PARTY = "ADD_PARTY";
export const REMOVE_PARTY = "REMOVE_PARTY";
export const CANCEL_CONFIRM_FILE_UPDATE = "CANCEL_CONFIRM_FILE_UPDATE";

export interface ModalProps {
modalType: string;
Expand Down
Loading