Make modal footer fixed (#6696) (#6699)

* Quick attempt at fixed form footer

* slightly improve on lower res devices

* Squashed commit of the following:

commit 06c7ebfc21
Author: Oliver <oliver.henry.walters@gmail.com>
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 a00d5ab4b5
Author: Oliver <oliver.henry.walters@gmail.com>
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 160d014e44
Author: Oliver <oliver.henry.walters@gmail.com>
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 57a1a81e9b
Author: Oliver <oliver.henry.walters@gmail.com>
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 0196dd2f60
Author: Lavissa <lavissawow@gmail.com>
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 6abd33f060
Author: Oliver <oliver.henry.walters@gmail.com>
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 cbd94fc4b5
Author: Oliver <oliver.henry.walters@gmail.com>
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 ec5ff6408d
Author: Lukas <76838159+wolflu05@users.noreply.github.com>
Date:   Thu Mar 14 13:03:30 2024 +0100

    handle report previewing errors (#6709)

commit 267ff67f05
Author: Oliver <oliver.henry.walters@gmail.com>
Date:   Thu Mar 14 15:11:27 2024 +1100

    [PUI] Updates (#6707)

    * Add button to edit part category

    * Fix useMemo()

    * Edit stock location

commit 610ea7b0b1
Author: Oliver <oliver.henry.walters@gmail.com>
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 7de87383b5
Author: Oliver <oliver.henry.walters@gmail.com>
Date:   Wed Mar 13 21:37:56 2024 +1100

    Update .env (#6700)

    Fix comment - no need to change Caddyfile in most cases

commit 2fef34852c
Author: Oliver <oliver.henry.walters@gmail.com>
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
This commit is contained in:
Xander Luciano 2024-03-20 03:00:43 -07:00 committed by GitHub
parent 5de56f5cd8
commit 97ec4d00ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 188 additions and 138 deletions

View File

@ -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 <LoadingOverlay visible={true} />;
}
return <ApiForm id={id} props={formProps} />;
}
@ -222,12 +224,14 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
props.pathParams
],
queryFn: async () => {
return api
.get(url)
.then((response) => {
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 = {};
// TODO: replace with .map()
for (const [k, field] of Object.entries(fields)) {
const dataValue = data[k];
@ -244,6 +248,8 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
return res;
};
// Process API response
const initialData: any = processFields(
props.fields ?? {},
response.data
@ -253,10 +259,11 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
form.reset(initialData);
return response;
})
.catch((error) => {
} 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<SubmitErrorHandler<FieldValues>>(() => {
@ -387,8 +398,15 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
return (
<Stack>
{/* Show loading overlay while fetching fields */}
{/* zIndex used to force overlay on top of modal header bar */}
<LoadingOverlay visible={isLoading} zIndex={1010} />
{/* Attempt at making fixed footer with scroll area */}
<Paper mah={'65vh'} style={{ overflowY: 'auto' }}>
<div>
{/* Form Fields */}
<Stack spacing="sm">
<LoadingOverlay visible={isLoading} />
{(!isValid || nonFieldErrors.length > 0) && (
<Alert radius="sm" color="red" title={t`Form Errors Exist`}>
{nonFieldErrors.length > 0 && (
@ -413,19 +431,25 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
)}
<FormProvider {...form}>
<Stack spacing="xs">
{Object.entries(props.fields ?? {}).map(([fieldName, field]) => (
{Object.entries(props.fields ?? {}).map(
([fieldName, field]) => (
<ApiFormField
key={fieldName}
fieldName={fieldName}
definition={field}
control={form.control}
/>
))}
)
)}
</Stack>
</FormProvider>
{props.postFormContent}
</Stack>
<Divider />
</div>
</Paper>
{/* Footer with Action Buttons */}
<div>
<Group position="right">
{props.actions?.map((action, i) => (
<Button
@ -448,6 +472,7 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
{props.submitText ?? t`Submit`}
</Button>
</Group>
</div>
</Stack>
);
}

View File

@ -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,9 +130,11 @@ export function UserDrawer({
id={`user-detail-drawer-${id}`}
/>
<Stack>
<Title order={5}>
<Trans>Groups</Trans>
</Title>
<Spoiler maxHeight={125} showLabel="Show More" hideLabel="Show Less">
<Text ml={'md'}>
{userDetail?.groups && userDetail?.groups?.length > 0 ? (
<List>
@ -139,6 +151,8 @@ export function UserDrawer({
<Trans>No groups</Trans>
)}
</Text>
</Spoiler>
</Stack>
</Stack>
);
}
@ -194,6 +208,9 @@ export function UserTable() {
];
}, []);
// Row Actions
const [selectedUser, setSelectedUser] = useState<number>(-1);
const rowActions = useCallback((record: UserDetailI): RowAction[] => {
return [
RowEditAction({
@ -201,21 +218,24 @@ 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({
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: {
@ -227,13 +247,16 @@ export function UserTable() {
onFormSuccess: table.refreshTable,
successMessage: t`Added user`
});
}, []);
const tableActions = useMemo(() => {
let actions = [];
actions.push(
<AddItemButton key="add-user" onClick={addUser} tooltip={t`Add user`} />
<AddItemButton
key="add-user"
onClick={newUser.open}
tooltip={t`Add user`}
/>
);
return actions;
@ -241,6 +264,8 @@ export function UserTable() {
return (
<>
{newUser.modal}
{deleteUser.modal}
<DetailDrawer
title={t`Edit user`}
renderContent={(id) => {