From 3e178d9e7123b036c769b7c4e3382ccfdbeb4d01 Mon Sep 17 00:00:00 2001 From: magsyg Date: Sat, 7 Dec 2024 20:16:53 +0100 Subject: [PATCH 1/4] fixes --- .../src/Components/InputFile/InputFile.tsx | 78 ++++++++++++------- frontend/src/Forms/SamfFormFieldTypes.tsx | 2 + .../RecruitmentApplicationFormPage.tsx | 3 + 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/frontend/src/Components/InputFile/InputFile.tsx b/frontend/src/Components/InputFile/InputFile.tsx index 3437d6249..f582b2bc5 100644 --- a/frontend/src/Components/InputFile/InputFile.tsx +++ b/frontend/src/Components/InputFile/InputFile.tsx @@ -5,23 +5,25 @@ import { useTranslation } from 'react-i18next'; import { KEY } from '~/i18n/constants'; import { TimeDisplay } from '../TimeDisplay'; import styles from './InputFile.module.scss'; +import { Link } from '../Link'; -export type InputFileType = 'image' | 'pdf'; +export type InputFileType = 'image' | 'pdf' | 'any'; export type InputFileProps = { - fileType: InputFileType; + fileType?: InputFileType; label?: ReactNode; + existing_url?: string; error?: boolean | string; - onSelected: (file: File) => void; + onSelected?: (file: File) => void; }; -export function InputFile({ fileType, label, error = false, onSelected }: InputFileProps) { +export function InputFile({ fileType='any', label, existing_url, error = false, onSelected }: InputFileProps) { const { t } = useTranslation(); const [selectedFile, setSelectedFile] = useState(undefined); const [preview, setPreview] = useState(undefined); function handleOnChange(e?: ChangeEvent) { - if (e === undefined) return; + if (e === undefined || onSelected === undefined) return; if (!e.target.files || e.target.files.length === 0) { setSelectedFile(undefined); } else { @@ -58,10 +60,11 @@ export function InputFile({ fileType, label, error = false, onSelected }: InputF const icons: Record = { image: 'mdi:image', pdf: 'mdi:file', + any: 'mdi:file', }; const horizontalPreview = fileType === 'pdf'; - const typePreviewClass = `preview_${fileType.toLowerCase()}`; + const typePreviewClass = `preview_${fileType?.toLowerCase()}`; const fileSizeMb = ((selectedFile?.size ?? 0) / 1024 / 1024).toFixed(2); const isError = error !== false; @@ -69,6 +72,8 @@ export function InputFile({ fileType, label, error = false, onSelected }: InputF
{/* Visual label */} + {existing_url && {existing_url}} + {/* Label container to accept button input */}
- {fileType === 'image' && ( - {selectedFile?.name ?? t(KEY.inputfile_no_file_selected)} - )} - - - {/* File Preview */} -
- {/* PDF shows additional information */} - {fileType === 'pdf' && ( -
-

{selectedFile?.name ?? t(KEY.inputfile_no_file_selected)}

-

- -

-

{fileSizeMb} MB

-
- )} - {/* Image/pdf preview. Shows empty preview for pdf type */} - {(fileType === 'pdf' || preview) && ( -
- {preview && Preview} -
- )} + { + (existing_url && !selectedFile) ? + ( + {existing_url} + ): + ( + {selectedFile?.name ?? t(KEY.inputfile_no_file_selected)} + ) + + + }
); } + + +/** + * +
+ {fileType === 'pdf' && ( +
+

{selectedFile?.name ?? t(KEY.inputfile_no_file_selected)}

+

+ +

+

{fileSizeMb} MB

+
+ )} + {fileType === 'any' && ( +
+

{selectedFile?.name ?? t(KEY.inputfile_no_file_selected)}

+

{fileSizeMb} MB

+
+ )} + {(fileType === 'pdf' || (fileType === 'image' || preview)) && ( +
+ {preview && Preview} +
+ )} +
+ */ diff --git a/frontend/src/Forms/SamfFormFieldTypes.tsx b/frontend/src/Forms/SamfFormFieldTypes.tsx index 13e4edd78..beb77bdd5 100644 --- a/frontend/src/Forms/SamfFormFieldTypes.tsx +++ b/frontend/src/Forms/SamfFormFieldTypes.tsx @@ -103,6 +103,7 @@ export type SamfFormFieldType = | 'date_time' | 'date' | 'time' + | 'file' | 'upload_image' | 'upload_pdf' | 'phonenumber'; @@ -132,6 +133,7 @@ export const SamfFormGenerators: Record('datetime-local'), date: makeStandardInputFunction('date'), time: makeStandardInputFunction('time'), + file: makeFilePickerFunction('any'), upload_image: makeFilePickerFunction('image'), upload_pdf: makeFilePickerFunction('pdf'), phonenumber: makePhoneNumberInput, diff --git a/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx b/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx index fe6f0d720..724e7b790 100644 --- a/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx +++ b/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx @@ -209,6 +209,9 @@ export function RecruitmentApplicationFormPage() { >

{t(KEY.recruitment_application)}:

{' '} + + + ) : (
From f66843791f088ea359e2870eb0b1e063a50122e5 Mon Sep 17 00:00:00 2001 From: magsyg Date: Sat, 7 Dec 2024 21:07:49 +0100 Subject: [PATCH 2/4] feat: fixup file input to any --- .../InputFile/InputFile.module.scss | 1 + .../src/Components/InputFile/InputFile.tsx | 62 +++++++++---------- .../RecruitmentApplicationFormPage.tsx | 4 +- frontend/src/utils.ts | 9 +++ 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/frontend/src/Components/InputFile/InputFile.module.scss b/frontend/src/Components/InputFile/InputFile.module.scss index aef0206c2..f104dbf09 100644 --- a/frontend/src/Components/InputFile/InputFile.module.scss +++ b/frontend/src/Components/InputFile/InputFile.module.scss @@ -123,6 +123,7 @@ white-space: nowrap; overflow: hidden; text-align: right; + pointer-events: all; @include for-mobile-only { flex-direction: column; } diff --git a/frontend/src/Components/InputFile/InputFile.tsx b/frontend/src/Components/InputFile/InputFile.tsx index f582b2bc5..7a5388dfc 100644 --- a/frontend/src/Components/InputFile/InputFile.tsx +++ b/frontend/src/Components/InputFile/InputFile.tsx @@ -6,6 +6,7 @@ import { KEY } from '~/i18n/constants'; import { TimeDisplay } from '../TimeDisplay'; import styles from './InputFile.module.scss'; import { Link } from '../Link'; +import { getFileNameFromUrl, isFileImage } from '~/utils'; export type InputFileType = 'image' | 'pdf' | 'any'; @@ -21,6 +22,8 @@ export function InputFile({ fileType='any', label, existing_url, error = false, const { t } = useTranslation(); const [selectedFile, setSelectedFile] = useState(undefined); const [preview, setPreview] = useState(undefined); + const [isImage, setIsImage] = useState(false); + function handleOnChange(e?: ChangeEvent) { if (e === undefined || onSelected === undefined) return; @@ -30,6 +33,9 @@ export function InputFile({ fileType='any', label, existing_url, error = false, setSelectedFile(e.target.files?.[0]); if (e.target.files?.[0] !== undefined) { onSelected(e.target.files?.[0]); + setIsImage(isFileImage(e.target.files?.[0].name)); + } else { + setIsImage(false); } } } @@ -56,7 +62,7 @@ export function InputFile({ fileType='any', label, existing_url, error = false, } return '*'; } - + const icons: Record = { image: 'mdi:image', pdf: 'mdi:file', @@ -72,8 +78,6 @@ export function InputFile({ fileType='any', label, existing_url, error = false,
{/* Visual label */} - {existing_url && {existing_url}} - {/* Label container to accept button input */}
+ +
+ {preview && + <> +
+

+ +

+

{fileSizeMb} MB

+
+ {isImage && ( +
+ {preview && Preview} +
+ ) + } + + } +
); } - - -/** - * -
- {fileType === 'pdf' && ( -
-

{selectedFile?.name ?? t(KEY.inputfile_no_file_selected)}

-

- -

-

{fileSizeMb} MB

-
- )} - {fileType === 'any' && ( -
-

{selectedFile?.name ?? t(KEY.inputfile_no_file_selected)}

-

{fileSizeMb} MB

-
- )} - {(fileType === 'pdf' || (fileType === 'image' || preview)) && ( -
- {preview && Preview} -
- )} -
- */ diff --git a/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx b/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx index 724e7b790..5a49bb56c 100644 --- a/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx +++ b/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx @@ -209,9 +209,7 @@ export function RecruitmentApplicationFormPage() { >

{t(KEY.recruitment_application)}:

{' '} - - - + ) : (
diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index 8e1d50750..55a54e025 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -151,6 +151,15 @@ export function getFullName(u: UserDto): string { return `${u.first_name} ${u.last_name}`.trim(); } +export function getFileNameFromUrl(url: string): string { + let url_split = url.split('/'); + return url_split[url_split.length-1]; +} + +export function isFileImage(file_name: string): boolean { + return file_name.match(/\.(jpg|jpeg|png|gif)$/i) != null; +} + /** Helper to determine if a KeyValue is truthy. */ export function isTruthy(value = ''): boolean { const falsy = ['', 'no', 'zero', '0']; From 10c66bca9527562c906072fbd9f51f7cffe54fcd Mon Sep 17 00:00:00 2001 From: magsyg Date: Sat, 7 Dec 2024 21:10:12 +0100 Subject: [PATCH 3/4] biome fix --- .../src/Components/InputFile/InputFile.tsx | 62 +++++++++---------- .../RecruitmentApplicationFormPage.tsx | 6 +- frontend/src/utils.ts | 4 +- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/frontend/src/Components/InputFile/InputFile.tsx b/frontend/src/Components/InputFile/InputFile.tsx index 7a5388dfc..f9faf56f9 100644 --- a/frontend/src/Components/InputFile/InputFile.tsx +++ b/frontend/src/Components/InputFile/InputFile.tsx @@ -3,10 +3,10 @@ import classNames from 'classnames'; import { type ChangeEvent, type ReactNode, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { KEY } from '~/i18n/constants'; +import { getFileNameFromUrl, isFileImage } from '~/utils'; +import { Link } from '../Link'; import { TimeDisplay } from '../TimeDisplay'; import styles from './InputFile.module.scss'; -import { Link } from '../Link'; -import { getFileNameFromUrl, isFileImage } from '~/utils'; export type InputFileType = 'image' | 'pdf' | 'any'; @@ -18,13 +18,12 @@ export type InputFileProps = { onSelected?: (file: File) => void; }; -export function InputFile({ fileType='any', label, existing_url, error = false, onSelected }: InputFileProps) { +export function InputFile({ fileType = 'any', label, existing_url, error = false, onSelected }: InputFileProps) { const { t } = useTranslation(); const [selectedFile, setSelectedFile] = useState(undefined); const [preview, setPreview] = useState(undefined); const [isImage, setIsImage] = useState(false); - function handleOnChange(e?: ChangeEvent) { if (e === undefined || onSelected === undefined) return; if (!e.target.files || e.target.files.length === 0) { @@ -62,7 +61,7 @@ export function InputFile({ fileType='any', label, existing_url, error = false, } return '*'; } - + const icons: Record = { image: 'mdi:image', pdf: 'mdi:file', @@ -88,37 +87,32 @@ export function InputFile({ fileType='any', label, existing_url, error = false, {t(KEY.inputfile_choose_a_file)}
- { - (existing_url && !selectedFile) ? - ( - {getFileNameFromUrl(existing_url)} - ): - ( - {selectedFile?.name ?? t(KEY.inputfile_no_file_selected)} - ) - - - } + {existing_url && !selectedFile ? ( + + {getFileNameFromUrl(existing_url)} + + ) : ( + {selectedFile?.name ?? t(KEY.inputfile_no_file_selected)} + )} -
- {preview && - <> -
-

- -

-

{fileSizeMb} MB

-
- {isImage && ( -
- {preview && Preview} -
- ) - } - - } -
+
+ {preview && ( + <> +
+

+ +

+

{fileSizeMb} MB

+
+ {isImage && ( +
+ {preview && Preview} +
+ )} + + )} +
); diff --git a/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx b/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx index 5a49bb56c..b5d005731 100644 --- a/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx +++ b/frontend/src/Pages/RecruitmentApplicationFormPage/RecruitmentApplicationFormPage.tsx @@ -209,7 +209,11 @@ export function RecruitmentApplicationFormPage() { >

{t(KEY.recruitment_application)}:

{' '} - + ) : (
diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index 55a54e025..6d40cd0ef 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -152,8 +152,8 @@ export function getFullName(u: UserDto): string { } export function getFileNameFromUrl(url: string): string { - let url_split = url.split('/'); - return url_split[url_split.length-1]; + const url_split = url.split('/'); + return url_split[url_split.length - 1]; } export function isFileImage(file_name: string): boolean { From 71553b1d4bd583ad5d58b361cd66de07642f6996 Mon Sep 17 00:00:00 2001 From: magsyg Date: Sat, 7 Dec 2024 21:26:50 +0100 Subject: [PATCH 4/4] feat: add existing image and input for custom accepted types --- .../src/Components/InputFile/InputFile.tsx | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/frontend/src/Components/InputFile/InputFile.tsx b/frontend/src/Components/InputFile/InputFile.tsx index f9faf56f9..511a333da 100644 --- a/frontend/src/Components/InputFile/InputFile.tsx +++ b/frontend/src/Components/InputFile/InputFile.tsx @@ -15,10 +15,18 @@ export type InputFileProps = { label?: ReactNode; existing_url?: string; error?: boolean | string; + acceptedTypes?: string; onSelected?: (file: File) => void; }; -export function InputFile({ fileType = 'any', label, existing_url, error = false, onSelected }: InputFileProps) { +export function InputFile({ + fileType = 'any', + acceptedTypes, + label, + existing_url, + error = false, + onSelected, +}: InputFileProps) { const { t } = useTranslation(); const [selectedFile, setSelectedFile] = useState(undefined); const [preview, setPreview] = useState(undefined); @@ -52,14 +60,21 @@ export function InputFile({ fileType = 'any', label, existing_url, error = false return () => URL.revokeObjectURL(objectUrl); }, [selectedFile]); + useEffect(() => { + if (existing_url && isFileImage(existing_url)) { + setIsImage(true); + setPreview(existing_url); + } + }, [existing_url]); + function acceptTypes() { - switch (fileType) { - case 'image': - return 'image/*'; - case 'pdf': - return 'application/pdf'; + if (fileType === 'any' && !acceptedTypes) { + return '*'; } - return '*'; + let types = acceptedTypes ? acceptedTypes : ''; + if (fileType === 'image') types += ', image/*'; + if (fileType === 'pdf') types += ', application/pdf'; + return types; } const icons: Record = {