mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
* Quick attempt at fixed form footer * slightly improve on lower res devices * Squashed commit of the following: commit06c7ebfc21
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 commita00d5ab4b5
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 commit160d014e44
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 commit57a1a81e9b
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 commit0196dd2f60
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 commit6abd33f060
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 commitcbd94fc4b5
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 commitec5ff6408d
Author: Lukas <76838159+wolflu05@users.noreply.github.com> Date: Thu Mar 14 13:03:30 2024 +0100 handle report previewing errors (#6709) commit267ff67f05
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 commit610ea7b0b1
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 commit7de87383b5
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 commit2fef34852c
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:
parent
5de56f5cd8
commit
97ec4d00ef
@ -2,15 +2,15 @@ import { t } from '@lingui/macro';
|
|||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
DefaultMantineColor,
|
DefaultMantineColor,
|
||||||
Divider,
|
|
||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
|
Paper,
|
||||||
Text
|
Text
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Button, Group, Stack } from '@mantine/core';
|
import { Button, Group, Stack } from '@mantine/core';
|
||||||
import { useId } from '@mantine/hooks';
|
import { useId } from '@mantine/hooks';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
FieldValues,
|
FieldValues,
|
||||||
@ -139,6 +139,12 @@ export function OptionsApiForm({
|
|||||||
const formProps: ApiFormProps = useMemo(() => {
|
const formProps: ApiFormProps = useMemo(() => {
|
||||||
const _props = { ...props };
|
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;
|
if (!_props.fields) return _props;
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(_props.fields)) {
|
for (const [k, v] of Object.entries(_props.fields)) {
|
||||||
@ -158,10 +164,6 @@ export function OptionsApiForm({
|
|||||||
return _props;
|
return _props;
|
||||||
}, [data, props]);
|
}, [data, props]);
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return <LoadingOverlay visible={true} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <ApiForm id={id} props={formProps} />;
|
return <ApiForm id={id} props={formProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,12 +224,14 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
|
|||||||
props.pathParams
|
props.pathParams
|
||||||
],
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return api
|
try {
|
||||||
.get(url)
|
// Await API call
|
||||||
.then((response) => {
|
let response = await api.get(url);
|
||||||
|
// Define function to process API response
|
||||||
const processFields = (fields: ApiFormFieldSet, data: NestedDict) => {
|
const processFields = (fields: ApiFormFieldSet, data: NestedDict) => {
|
||||||
const res: NestedDict = {};
|
const res: NestedDict = {};
|
||||||
|
|
||||||
|
// TODO: replace with .map()
|
||||||
for (const [k, field] of Object.entries(fields)) {
|
for (const [k, field] of Object.entries(fields)) {
|
||||||
const dataValue = data[k];
|
const dataValue = data[k];
|
||||||
|
|
||||||
@ -244,6 +248,8 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Process API response
|
||||||
const initialData: any = processFields(
|
const initialData: any = processFields(
|
||||||
props.fields ?? {},
|
props.fields ?? {},
|
||||||
response.data
|
response.data
|
||||||
@ -253,10 +259,11 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
|
|||||||
form.reset(initialData);
|
form.reset(initialData);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
})
|
} catch (error) {
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error fetching initial data:', 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(
|
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>>(() => {
|
const onFormError = useCallback<SubmitErrorHandler<FieldValues>>(() => {
|
||||||
@ -387,8 +398,15 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<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">
|
<Stack spacing="sm">
|
||||||
<LoadingOverlay visible={isLoading} />
|
|
||||||
{(!isValid || nonFieldErrors.length > 0) && (
|
{(!isValid || nonFieldErrors.length > 0) && (
|
||||||
<Alert radius="sm" color="red" title={t`Form Errors Exist`}>
|
<Alert radius="sm" color="red" title={t`Form Errors Exist`}>
|
||||||
{nonFieldErrors.length > 0 && (
|
{nonFieldErrors.length > 0 && (
|
||||||
@ -413,19 +431,25 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
|
|||||||
)}
|
)}
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
{Object.entries(props.fields ?? {}).map(([fieldName, field]) => (
|
{Object.entries(props.fields ?? {}).map(
|
||||||
|
([fieldName, field]) => (
|
||||||
<ApiFormField
|
<ApiFormField
|
||||||
key={fieldName}
|
key={fieldName}
|
||||||
fieldName={fieldName}
|
fieldName={fieldName}
|
||||||
definition={field}
|
definition={field}
|
||||||
control={form.control}
|
control={form.control}
|
||||||
/>
|
/>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
{props.postFormContent}
|
{props.postFormContent}
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider />
|
</div>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Footer with Action Buttons */}
|
||||||
|
<div>
|
||||||
<Group position="right">
|
<Group position="right">
|
||||||
{props.actions?.map((action, i) => (
|
{props.actions?.map((action, i) => (
|
||||||
<Button
|
<Button
|
||||||
@ -448,6 +472,7 @@ export function ApiForm({ id, props }: { id: string; props: ApiFormProps }) {
|
|||||||
{props.submitText ?? t`Submit`}
|
{props.submitText ?? t`Submit`}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import { Trans, t } from '@lingui/macro';
|
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 { IconInfoCircle } from '@tabler/icons-react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { EditApiForm } from '../../components/forms/ApiForm';
|
import { EditApiForm } from '../../components/forms/ApiForm';
|
||||||
@ -12,7 +19,10 @@ import {
|
|||||||
DetailDrawerLink
|
DetailDrawerLink
|
||||||
} from '../../components/nav/DetailDrawer';
|
} from '../../components/nav/DetailDrawer';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { openCreateApiForm, openDeleteApiForm } from '../../functions/forms';
|
import {
|
||||||
|
useCreateApiFormModal,
|
||||||
|
useDeleteApiFormModal
|
||||||
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
@ -120,9 +130,11 @@ export function UserDrawer({
|
|||||||
id={`user-detail-drawer-${id}`}
|
id={`user-detail-drawer-${id}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Stack>
|
||||||
<Title order={5}>
|
<Title order={5}>
|
||||||
<Trans>Groups</Trans>
|
<Trans>Groups</Trans>
|
||||||
</Title>
|
</Title>
|
||||||
|
<Spoiler maxHeight={125} showLabel="Show More" hideLabel="Show Less">
|
||||||
<Text ml={'md'}>
|
<Text ml={'md'}>
|
||||||
{userDetail?.groups && userDetail?.groups?.length > 0 ? (
|
{userDetail?.groups && userDetail?.groups?.length > 0 ? (
|
||||||
<List>
|
<List>
|
||||||
@ -139,6 +151,8 @@ export function UserDrawer({
|
|||||||
<Trans>No groups</Trans>
|
<Trans>No groups</Trans>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
</Spoiler>
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -194,6 +208,9 @@ export function UserTable() {
|
|||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Row Actions
|
||||||
|
const [selectedUser, setSelectedUser] = useState<number>(-1);
|
||||||
|
|
||||||
const rowActions = useCallback((record: UserDetailI): RowAction[] => {
|
const rowActions = useCallback((record: UserDetailI): RowAction[] => {
|
||||||
return [
|
return [
|
||||||
RowEditAction({
|
RowEditAction({
|
||||||
@ -201,21 +218,24 @@ export function UserTable() {
|
|||||||
}),
|
}),
|
||||||
RowDeleteAction({
|
RowDeleteAction({
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
openDeleteApiForm({
|
setSelectedUser(record.pk);
|
||||||
url: ApiEndpoints.user_list,
|
deleteUser.open();
|
||||||
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?`
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const addUser = useCallback(() => {
|
const deleteUser = useDeleteApiFormModal({
|
||||||
openCreateApiForm({
|
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,
|
url: ApiEndpoints.user_list,
|
||||||
title: t`Add user`,
|
title: t`Add user`,
|
||||||
fields: {
|
fields: {
|
||||||
@ -227,13 +247,16 @@ export function UserTable() {
|
|||||||
onFormSuccess: table.refreshTable,
|
onFormSuccess: table.refreshTable,
|
||||||
successMessage: t`Added user`
|
successMessage: t`Added user`
|
||||||
});
|
});
|
||||||
}, []);
|
|
||||||
|
|
||||||
const tableActions = useMemo(() => {
|
const tableActions = useMemo(() => {
|
||||||
let actions = [];
|
let actions = [];
|
||||||
|
|
||||||
actions.push(
|
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;
|
return actions;
|
||||||
@ -241,6 +264,8 @@ export function UserTable() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{newUser.modal}
|
||||||
|
{deleteUser.modal}
|
||||||
<DetailDrawer
|
<DetailDrawer
|
||||||
title={t`Edit user`}
|
title={t`Edit user`}
|
||||||
renderContent={(id) => {
|
renderContent={(id) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user