diff --git a/src/frontend/src/forms/AttachmentForms.tsx b/src/frontend/src/forms/AttachmentForms.tsx deleted file mode 100644 index 4e10655337..0000000000 --- a/src/frontend/src/forms/AttachmentForms.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { t } from '@lingui/macro'; - -import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField'; -import { ApiEndpoints } from '../enums/ApiEndpoints'; -import { - openCreateApiForm, - openDeleteApiForm, - openEditApiForm -} from '../functions/forms'; - -export function attachmentFields(editing: boolean): ApiFormFieldSet { - let fields: ApiFormFieldSet = { - attachment: {}, - comment: {} - }; - - if (editing) { - delete fields['attachment']; - } - - return fields; -} - -/** - * Add a new attachment (either a file or a link) - */ -export function addAttachment({ - endpoint, - model, - pk, - attachmentType, - callback -}: { - endpoint: ApiEndpoints; - model: string; - pk: number; - attachmentType: 'file' | 'link'; - callback?: () => void; -}) { - let formFields: ApiFormFieldSet = { - attachment: {}, - link: {}, - comment: {} - }; - - if (attachmentType === 'link') { - delete formFields['attachment']; - } else { - delete formFields['link']; - } - - formFields[model] = { - value: pk, - hidden: true - }; - - let title = attachmentType === 'file' ? t`Add File` : t`Add Link`; - let message = attachmentType === 'file' ? t`File added` : t`Link added`; - - openCreateApiForm({ - title: title, - url: endpoint, - successMessage: message, - fields: formFields, - onFormSuccess: callback - }); -} - -/** - * Edit an existing attachment (either a file or a link) - */ -export function editAttachment({ - endpoint, - model, - pk, - attachmentType, - callback -}: { - endpoint: ApiEndpoints; - model: string; - pk: number; - attachmentType: 'file' | 'link'; - callback?: (record: any) => void; -}) { - let formFields: ApiFormFieldSet = { - link: {}, - comment: {} - }; - - if (attachmentType === 'file') { - delete formFields['link']; - } - - formFields[model] = { - value: pk, - hidden: true - }; - - let title = attachmentType === 'file' ? t`Edit File` : t`Edit Link`; - let message = attachmentType === 'file' ? t`File updated` : t`Link updated`; - - openEditApiForm({ - title: title, - url: endpoint, - pk: pk, - successMessage: message, - fields: formFields, - onFormSuccess: callback - }); -} - -export function deleteAttachment({ - endpoint, - pk, - callback -}: { - endpoint: ApiEndpoints; - pk: number; - callback: () => void; -}) { - openDeleteApiForm({ - url: endpoint, - pk: pk, - title: t`Delete Attachment`, - successMessage: t`Attachment deleted`, - onFormSuccess: callback, - fields: {}, - preFormWarning: t`Are you sure you want to delete this attachment?` - }); -} diff --git a/src/frontend/src/tables/InvenTreeTable.tsx b/src/frontend/src/tables/InvenTreeTable.tsx index 28208f1d8a..35450786d9 100644 --- a/src/frontend/src/tables/InvenTreeTable.tsx +++ b/src/frontend/src/tables/InvenTreeTable.tsx @@ -542,8 +542,6 @@ export function InvenTreeTable({ record: any; index: number; }) => { - cancelEvent(event); - if (props.onRowClick) { // If a custom row click handler is provided, use that props.onRowClick(record, index, event); @@ -552,6 +550,7 @@ export function InvenTreeTable({ const pk = resolveItem(record, accessor); if (pk) { + cancelEvent(event); // If a model type is provided, navigate to the detail view for that model let url = getDetailUrl(tableProps.modelType, pk); navigateToLink(url, navigate, event); @@ -564,12 +563,14 @@ export function InvenTreeTable({ return ( <> {tableProps.enableFilters && (filters.length ?? 0) > 0 && ( - setFiltersVisible(false)} - /> + + setFiltersVisible(false)} + /> + )} diff --git a/src/frontend/src/tables/general/AttachmentTable.tsx b/src/frontend/src/tables/general/AttachmentTable.tsx index 1a3a68f576..49949036be 100644 --- a/src/frontend/src/tables/general/AttachmentTable.tsx +++ b/src/frontend/src/tables/general/AttachmentTable.tsx @@ -1,18 +1,33 @@ import { t } from '@lingui/macro'; -import { ActionIcon, Badge, Group, Stack, Text, Tooltip } from '@mantine/core'; +import { + ActionIcon, + Badge, + Group, + Paper, + Stack, + Text, + Tooltip, + rem +} from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { notifications } from '@mantine/notifications'; -import { IconExternalLink, IconFileUpload } from '@tabler/icons-react'; +import { + IconExternalLink, + IconFileUpload, + IconUpload, + IconX +} from '@tabler/icons-react'; import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; import { api } from '../../App'; +import { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; import { AttachmentLink } from '../../components/items/AttachmentLink'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { - addAttachment, - deleteAttachment, - editAttachment -} from '../../forms/AttachmentForms'; + useCreateApiFormModal, + useDeleteApiFormModal, + useEditApiFormModal +} from '../../hooks/UseForm'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; import { TableColumn } from '../Column'; @@ -103,49 +118,9 @@ export function AttachmentTable({ .catch((error) => { return error; }); - }, []); + }, [url]); - // Construct row actions for the attachment table - const rowActions = useCallback( - (record: any) => { - let actions: RowAction[] = []; - - if (allowEdit) { - actions.push( - RowEditAction({ - onClick: () => { - editAttachment({ - endpoint: endpoint, - model: model, - pk: record.pk, - attachmentType: record.attachment ? 'file' : 'link', - callback: (record: any) => { - table.updateRecord(record); - } - }); - } - }) - ); - } - - if (allowDelete) { - actions.push( - RowDeleteAction({ - onClick: () => { - deleteAttachment({ - endpoint: endpoint, - pk: record.pk, - callback: table.refreshTable - }); - } - }) - ); - } - - return actions; - }, - [allowEdit, allowDelete] - ); + const [isUploading, setIsUploading] = useState(false); // Callback to upload file attachment(s) function uploadFiles(files: File[]) { @@ -154,6 +129,8 @@ export function AttachmentTable({ formData.append('attachment', file); formData.append(model, pk.toString()); + setIsUploading(true); + api .post(url, formData) .then((response) => { @@ -175,10 +152,76 @@ export function AttachmentTable({ color: 'red' }); return error; + }) + .finally(() => { + setIsUploading(false); }); }); } + const [attachmentType, setAttachmentType] = useState<'attachment' | 'link'>( + 'attachment' + ); + + const [selectedAttachment, setSelectedAttachment] = useState< + number | undefined + >(undefined); + + const uploadFields: ApiFormFieldSet = useMemo(() => { + let fields: ApiFormFieldSet = { + [model]: { + value: pk, + hidden: true + }, + attachment: {}, + link: {}, + comment: {} + }; + + if (attachmentType != 'link') { + delete fields['link']; + } + + // Remove the 'attachment' field if we are editing an existing attachment, or uploading a link + if (attachmentType != 'attachment' || !!selectedAttachment) { + delete fields['attachment']; + } + + return fields; + }, [endpoint, model, pk, attachmentType, selectedAttachment]); + + const uploadAttachment = useCreateApiFormModal({ + url: endpoint, + title: t`Upload Attachment`, + fields: uploadFields, + onFormSuccess: () => { + table.refreshTable(); + } + }); + + const editAttachment = useEditApiFormModal({ + url: endpoint, + pk: selectedAttachment, + title: t`Edit Attachment`, + fields: uploadFields, + onFormSuccess: (record: any) => { + if (record.pk) { + table.updateRecord(record); + } else { + table.refreshTable(); + } + } + }); + + const deleteAttachment = useDeleteApiFormModal({ + url: endpoint, + pk: selectedAttachment, + title: t`Delete Attachment`, + onFormSuccess: () => { + table.refreshTable(); + } + }); + const tableActions: ReactNode[] = useMemo(() => { let actions = []; @@ -188,13 +231,9 @@ export function AttachmentTable({ { - addAttachment({ - endpoint: endpoint, - model: model, - pk: pk, - attachmentType: 'file', - callback: table.refreshTable - }); + setAttachmentType('attachment'); + setSelectedAttachment(undefined); + uploadAttachment.open(); }} variant="transparent" > @@ -208,13 +247,9 @@ export function AttachmentTable({ { - addAttachment({ - endpoint: endpoint, - model: model, - pk: pk, - attachmentType: 'link', - callback: table.refreshTable - }); + setAttachmentType('link'); + setSelectedAttachment(undefined); + uploadAttachment.open(); }} variant="transparent" > @@ -227,35 +262,93 @@ export function AttachmentTable({ return actions; }, [allowEdit]); - return ( - - {pk && pk > 0 && ( - { + let actions: RowAction[] = []; + + if (allowEdit) { + actions.push( + RowEditAction({ + onClick: () => { + setSelectedAttachment(record.pk); + editAttachment.open(); } - }} - /> - )} - {allowEdit && validPk && ( - - - - - {t`Upload attachment`} - - - - )} - + }) + ); + } + + if (allowDelete) { + actions.push( + RowDeleteAction({ + onClick: () => { + setSelectedAttachment(record.pk); + deleteAttachment.open(); + } + }) + ); + } + + return actions; + }, + [allowEdit, allowDelete] + ); + + return ( + <> + {uploadAttachment.modal} + {editAttachment.modal} + {deleteAttachment.modal} + + {pk && pk > 0 && ( + + )} + {allowEdit && validPk && ( + + + + + + + + + + + + + {t`Drag attachment file here to upload`} + + + + )} + + ); } diff --git a/src/frontend/tests/pages/pui_part.spec.ts b/src/frontend/tests/pages/pui_part.spec.ts index f0e21f108e..dfd45a60e7 100644 --- a/src/frontend/tests/pages/pui_part.spec.ts +++ b/src/frontend/tests/pages/pui_part.spec.ts @@ -151,3 +151,11 @@ test('PUI - Pages - Part - Pricing (Purchase)', async ({ page }) => { .waitFor(); await page.getByText('2022-04-29').waitFor(); }); + +test('PUI - Pages - Part - Attachments', async ({ page }) => { + await doQuickLogin(page); + + await page.goto(`${baseUrl}/part/69/attachments`); + + await page.waitForTimeout(5000); +});