From 97ec4d00efff5521b363f477bf4a566c532662bf Mon Sep 17 00:00:00 2001 From: Xander Luciano Date: Wed, 20 Mar 2024 03:00:43 -0700 Subject: [PATCH] Make modal footer fixed (#6696) (#6699) * Quick attempt at fixed form footer * slightly improve on lower res devices * Squashed commit of the following: commit 06c7ebfc21938ccbedc468324e3c66b8f451b277 Author: Oliver Date: Sat Mar 16 09:11:57 2024 +1100 Update docker_install.md (#6723) * Update docker_install.md Add note about external access * Update docker_install.md commit a00d5ab4b593ff68317a46d3eb359093c936969f Author: Oliver Date: Fri Mar 15 17:53:58 2024 +1100 Disable BOM requirement (#6719) * Add new setting STOCK_ENFORCE_BOM_INSTALLATION - Defaults to True (legacy) * Add logic to bypass BOM check * Update CUI to reflect new logic * Render InstalledItemsTable in PUI commit 160d014e44ffa686bb0a6f2e96297a30856bb18e Author: Oliver Date: Fri Mar 15 17:12:53 2024 +1100 [PUI] Details Pages (#6718) * Add "details" view to SupplierPart page * Fix PartActions * Add placeholder for actions * Add "title" option to DetailsTable * Add edit form to supplier part page * Fix link to manufacturer part * Add "details" view to ManufacturerPartDetail page * Add edit for ManufacturerPart * Create new manufacturer part from company table * Tweak ActionIcon commit 57a1a81e9b0f7b5c2b16067da3b00865316c4ac0 Author: Oliver Date: Fri Mar 15 12:24:17 2024 +1100 Reporting: Build line label fix (#6717) * Fix "BuildLine" label in PUI - Point to "buildline" not "build" * Prevent escape closing template ediror * Update report docs * Fix for format_number - Prevent number from being represented as scientific notation commit 0196dd2f600191583e3e0b7a255fe90b7bdec7ea Author: Lavissa Date: Fri Mar 15 02:06:18 2024 +0100 [PUI/Feature] Integrate Part "Default Location" into UX (#5972) * Add default parts to location page * Fix name strings * Add Stock Transfer modal * Add ApiForm Table field * temp * Add stock transfer form to part, stock item and location * All stock operations for Item, Part, and Location added (except order new) * Add default_location category traversal, and initial PO Line Item Receive form * . * Remove debug values * Added PO line receive form * Add functionality to PO receive extra fields * . * Forgot to bump API version * Add Category Default to details panel * Fix stockItem query count * Fix reviewed issues * . * . * . * Prevent root category from checking parent for default location commit 6abd33f060ed471526ca8d6ce99acb12e2ecd944 Author: Oliver Date: Fri Mar 15 00:24:48 2024 +1100 Report enhancements (#6714) * Add "enabled" filter to template table * Cleanup * API endpoints - Add API endpoints for report snippet - List endpoint - Details endpoint * Update serializers - Add asset serializer - Update * Check for duplicate asset files - Prevent upload of duplicate asset files - Allow re-upload for same PK * Duplicate checks for ReportSnippet * Bump API version commit cbd94fc4b59f01d7cb15448bf5b071face7d2a1a Author: Oliver Date: Thu Mar 14 23:06:11 2024 +1100 Fix for caddyfile (#6712) - Add "authorization" to Access-Control-Allow-Headers - CORS requests actually *work* now commit ec5ff6408dd21e49f902eb5be53e73970223d1b7 Author: Lukas <76838159+wolflu05@users.noreply.github.com> Date: Thu Mar 14 13:03:30 2024 +0100 handle report previewing errors (#6709) commit 267ff67f05f350709cfec0c97b2813c819c05244 Author: Oliver Date: Thu Mar 14 15:11:27 2024 +1100 [PUI] Updates (#6707) * Add button to edit part category * Fix useMemo() * Edit stock location commit 610ea7b0b1b6316bc66883b429b3870e1d011cd4 Author: Oliver Date: Thu Mar 14 12:09:14 2024 +1100 Report: Add date rendering (#6706) * Validate timezone in settings.py * Add helper functions for timezone information - Extract server timezone - Convert provided time to specified timezone * Add more unit tests * Remove debug print * Test fix * Add report helper tags - format_date - format_datetime - Update report templates - Unit tests * Add setting to control report errors - Only log errors to DB if setting is enabled * Update example report * Fixes for to_local_time * Update type hinting * Fix unit test typo commit 7de87383b54032ae6df46b50b3a5e46f75fa3bd8 Author: Oliver Date: Wed Mar 13 21:37:56 2024 +1100 Update .env (#6700) Fix comment - no need to change Caddyfile in most cases commit 2fef34852c5c855c9be1793a120034cf8d1ff622 Author: Oliver Date: Wed Mar 13 20:37:05 2024 +1100 Unit tests for HOST settings (#6698) - CORS - ALLOWED_HOSTS * Make ApiForms shrinkable, spoiler long group list * Improve API Form Scroll Behavior * Fix incorrect modal component * Force load all modal fields to trigger loading animation * Show loading overlay while fetching fields --- src/frontend/src/components/forms/ApiForm.tsx | 209 ++++++++++-------- .../src/tables/settings/UserTable.tsx | 117 ++++++---- 2 files changed, 188 insertions(+), 138 deletions(-) diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx index dbdc37e132..742b1e94bd 100644 --- a/src/frontend/src/components/forms/ApiForm.tsx +++ b/src/frontend/src/components/forms/ApiForm.tsx @@ -2,15 +2,15 @@ import { t } from '@lingui/macro'; import { Alert, DefaultMantineColor, - Divider, LoadingOverlay, + Paper, Text } from '@mantine/core'; import { Button, Group, Stack } from '@mantine/core'; import { useId } from '@mantine/hooks'; import { notifications } from '@mantine/notifications'; import { useQuery } from '@tanstack/react-query'; -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useState } from 'react'; import { FieldValues, @@ -139,6 +139,12 @@ export function OptionsApiForm({ const formProps: ApiFormProps = useMemo(() => { const _props = { ...props }; + // This forcefully overrides initial data + // Currently, most modals do not get pre-loaded correctly + if (!data) { + _props.fields = undefined; + } + if (!_props.fields) return _props; for (const [k, v] of Object.entries(_props.fields)) { @@ -158,10 +164,6 @@ export function OptionsApiForm({ return _props; }, [data, props]); - if (!data) { - return ; - } - return ; } @@ -222,41 +224,46 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) { props.pathParams ], queryFn: async () => { - return api - .get(url) - .then((response) => { - const processFields = (fields: ApiFormFieldSet, data: NestedDict) => { - const res: NestedDict = {}; + try { + // Await API call + let response = await api.get(url); + // Define function to process API response + const processFields = (fields: ApiFormFieldSet, data: NestedDict) => { + const res: NestedDict = {}; - for (const [k, field] of Object.entries(fields)) { - const dataValue = data[k]; + // TODO: replace with .map() + for (const [k, field] of Object.entries(fields)) { + const dataValue = data[k]; - if ( - field.field_type === 'nested object' && - field.children && - typeof dataValue === 'object' - ) { - res[k] = processFields(field.children, dataValue); - } else { - res[k] = dataValue; - } + if ( + field.field_type === 'nested object' && + field.children && + typeof dataValue === 'object' + ) { + res[k] = processFields(field.children, dataValue); + } else { + res[k] = dataValue; } + } - return res; - }; - const initialData: any = processFields( - props.fields ?? {}, - response.data - ); + return res; + }; - // Update form values, but only for the fields specified for this form - form.reset(initialData); + // Process API response + const initialData: any = processFields( + props.fields ?? {}, + response.data + ); - return response; - }) - .catch((error) => { - console.error('Error fetching initial data:', error); - }); + // Update form values, but only for the fields specified for this form + form.reset(initialData); + + return response; + } catch (error) { + console.error('Error fetching initial data:', error); + // Re-throw error to allow react-query to handle error + throw error; + } } }); @@ -377,8 +384,12 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) { }; const isLoading = useMemo( - () => isFormLoading || initialDataQuery.isFetching || isSubmitting, - [isFormLoading, initialDataQuery.isFetching, isSubmitting] + () => + isFormLoading || + initialDataQuery.isFetching || + isSubmitting || + !props.fields, + [isFormLoading, initialDataQuery.isFetching, isSubmitting, props.fields] ); const onFormError = useCallback>(() => { @@ -387,67 +398,81 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) { return ( - - - {(!isValid || nonFieldErrors.length > 0) && ( - - {nonFieldErrors.length > 0 && ( - - {nonFieldErrors.map((message) => ( - {message} - ))} - + {/* Show loading overlay while fetching fields */} + {/* zIndex used to force overlay on top of modal header bar */} + + + {/* Attempt at making fixed footer with scroll area */} + +
+ {/* Form Fields */} + + {(!isValid || nonFieldErrors.length > 0) && ( + + {nonFieldErrors.length > 0 && ( + + {nonFieldErrors.map((message) => ( + {message} + ))} + + )} + )} - - )} - {props.preFormContent} - {props.preFormSuccess && ( - - {props.preFormSuccess} - - )} - {props.preFormWarning && ( - - {props.preFormWarning} - - )} - - - {Object.entries(props.fields ?? {}).map(([fieldName, field]) => ( - - ))} + {props.preFormContent} + {props.preFormSuccess && ( + + {props.preFormSuccess} + + )} + {props.preFormWarning && ( + + {props.preFormWarning} + + )} + + + {Object.entries(props.fields ?? {}).map( + ([fieldName, field]) => ( + + ) + )} + + + {props.postFormContent} - - {props.postFormContent} - - - - {props.actions?.map((action, i) => ( +
+
+ + {/* Footer with Action Buttons */} +
+ + {props.actions?.map((action, i) => ( + + ))} - ))} - - + +
); } diff --git a/src/frontend/src/tables/settings/UserTable.tsx b/src/frontend/src/tables/settings/UserTable.tsx index 87ccdc6d04..e2793d1c8d 100644 --- a/src/frontend/src/tables/settings/UserTable.tsx +++ b/src/frontend/src/tables/settings/UserTable.tsx @@ -1,9 +1,16 @@ import { Trans, t } from '@lingui/macro'; -import { Alert, List, LoadingOverlay, Stack, Text, Title } from '@mantine/core'; +import { + Alert, + List, + LoadingOverlay, + Spoiler, + Stack, + Text, + Title +} from '@mantine/core'; import { IconInfoCircle } from '@tabler/icons-react'; -import { useCallback, useMemo } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Link } from 'react-router-dom'; import { AddItemButton } from '../../components/buttons/AddItemButton'; import { EditApiForm } from '../../components/forms/ApiForm'; @@ -12,7 +19,10 @@ import { DetailDrawerLink } from '../../components/nav/DetailDrawer'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; -import { openCreateApiForm, openDeleteApiForm } from '../../functions/forms'; +import { + useCreateApiFormModal, + useDeleteApiFormModal +} from '../../hooks/UseForm'; import { useInstance } from '../../hooks/UseInstance'; import { useTable } from '../../hooks/UseTable'; import { apiUrl } from '../../states/ApiState'; @@ -120,25 +130,29 @@ export function UserDrawer({ id={`user-detail-drawer-${id}`} /> - - <Trans>Groups</Trans> - - - {userDetail?.groups && userDetail?.groups?.length > 0 ? ( - - {userDetail?.groups?.map((group) => ( - - - - ))} - - ) : ( - No groups - )} - + + + <Trans>Groups</Trans> + + + + {userDetail?.groups && userDetail?.groups?.length > 0 ? ( + + {userDetail?.groups?.map((group) => ( + + + + ))} + + ) : ( + No groups + )} + + +
); } @@ -194,6 +208,9 @@ export function UserTable() { ]; }, []); + // Row Actions + const [selectedUser, setSelectedUser] = useState(-1); + const rowActions = useCallback((record: UserDetailI): RowAction[] => { return [ RowEditAction({ @@ -201,39 +218,45 @@ export function UserTable() { }), RowDeleteAction({ onClick: () => { - openDeleteApiForm({ - url: ApiEndpoints.user_list, - pk: record.pk, - title: t`Delete user`, - successMessage: t`User deleted`, - onFormSuccess: table.refreshTable, - preFormWarning: t`Are you sure you want to delete this user?` - }); + setSelectedUser(record.pk); + deleteUser.open(); } }) ]; }, []); - const addUser = useCallback(() => { - openCreateApiForm({ - url: ApiEndpoints.user_list, - title: t`Add user`, - fields: { - username: {}, - email: {}, - first_name: {}, - last_name: {} - }, - onFormSuccess: table.refreshTable, - successMessage: t`Added user` - }); - }, []); + const deleteUser = useDeleteApiFormModal({ + url: ApiEndpoints.user_list, + pk: selectedUser, + title: t`Delete user`, + successMessage: t`User deleted`, + onFormSuccess: table.refreshTable, + preFormWarning: t`Are you sure you want to delete this user?` + }); + + // Table Actions - Add New User + const newUser = useCreateApiFormModal({ + url: ApiEndpoints.user_list, + title: t`Add user`, + fields: { + username: {}, + email: {}, + first_name: {}, + last_name: {} + }, + onFormSuccess: table.refreshTable, + successMessage: t`Added user` + }); const tableActions = useMemo(() => { let actions = []; actions.push( - + ); return actions; @@ -241,6 +264,8 @@ export function UserTable() { return ( <> + {newUser.modal} + {deleteUser.modal} {