diff --git a/docs/docs/settings/user.md b/docs/docs/settings/user.md index 674aef5197..1114e26382 100644 --- a/docs/docs/settings/user.md +++ b/docs/docs/settings/user.md @@ -22,7 +22,6 @@ The *Display Settings* screen shows general display configuration options: {{ usersetting("STICKY_HEADER") }} {{ usersetting("DATE_DISPLAY_FORMAT") }} {{ usersetting("FORMS_CLOSE_USING_ESCAPE") }} -{{ usersetting("PART_SHOW_QUANTITY_IN_FORMS") }} {{ usersetting("DISPLAY_STOCKTAKE_TAB") }} {{ usersetting("SHOW_FULL_CATEGORY_IN_TABLES")}} {{ usersetting("ENABLE_LAST_BREADCRUMB") }} diff --git a/src/backend/InvenTree/common/setting/user.py b/src/backend/InvenTree/common/setting/user.py index 6f4c7ab0e0..8e5a45bbe1 100644 --- a/src/backend/InvenTree/common/setting/user.py +++ b/src/backend/InvenTree/common/setting/user.py @@ -173,12 +173,6 @@ USER_SETTINGS: dict[str, InvenTreeSettingsKeyType] = { 'default': False, 'validator': bool, }, - 'PART_SHOW_QUANTITY_IN_FORMS': { - 'name': _('Show Quantity in Forms'), - 'description': _('Display available part quantity in some forms'), - 'default': True, - 'validator': bool, - }, 'FORMS_CLOSE_USING_ESCAPE': { 'name': _('Escape Key Closes Forms'), 'description': _('Use the escape key to close modal forms'), diff --git a/src/frontend/src/components/nav/Header.tsx b/src/frontend/src/components/nav/Header.tsx index c3c5e57054..4b5a3ed9c6 100644 --- a/src/frontend/src/components/nav/Header.tsx +++ b/src/frontend/src/components/nav/Header.tsx @@ -62,6 +62,7 @@ export function Header() { const { isLoggedIn } = useUserState(); const [notificationCount, setNotificationCount] = useState(0); const globalSettings = useGlobalSettingsState(); + const userSettings = useUserSettingsState(); const navbar_message = useMemo(() => { return server.customize?.navbar_message; @@ -107,8 +108,21 @@ export function Header() { else closeNavDrawer(); }, [navigationOpen]); + const headerStyle: any = useMemo(() => { + const sticky: boolean = userSettings.isSet('STICKY_HEADER', true); + + if (sticky) { + return { + position: 'sticky', + top: 0 + }; + } else { + return {}; + } + }, [userSettings]); + return ( -
+
{ - let cols = 1; - - if (!!detail) { - cols++; - } - - if (!!badges) { - cols++; - } - - return cols; - }, [detail, badges]); - // breadcrumb caching const computedBreadcrumbs = useMemo(() => { if (userSettings.isSet('ENABLE_LAST_BREADCRUMB', false)) { @@ -114,14 +98,13 @@ export function PageDetail({ wrap='nowrap' align='flex-start' > - - + {imageUrl && ( - {detail &&
{detail}
} {badges && ( - + {badges?.map((badge, idx) => ( {badge} ))} + )} -
+ {actions && ( {actions.map((action, idx) => ( diff --git a/src/frontend/src/hooks/UseModal.tsx b/src/frontend/src/hooks/UseModal.tsx index 284cd623ae..60a36f46c6 100644 --- a/src/frontend/src/hooks/UseModal.tsx +++ b/src/frontend/src/hooks/UseModal.tsx @@ -4,12 +4,15 @@ import { useCallback } from 'react'; import type { UseModalProps, UseModalReturn } from '@lib/types/Modals'; import { StylishText } from '../components/items/StylishText'; +import { useUserSettingsState } from '../states/SettingsStates'; export function useModal(props: UseModalProps): UseModalReturn { const onOpen = useCallback(() => { props.onOpen?.(); }, [props.onOpen]); + const userSettings = useUserSettingsState(); + const onClose = useCallback(() => { props.onClose?.(); }, [props.onClose]); @@ -27,6 +30,7 @@ export function useModal(props: UseModalProps): UseModalReturn { + RenderPart({ + instance: option.part, + showSecondary: false + }) + } + onChange={(value: any) => { + navigate(getDetailUrl(ModelType.part, value.value)); + }} + styles={{ + menuPortal: (base: any) => ({ ...base, zIndex: 9999 }), + menu: (base: any) => ({ ...base, zIndex: 9999 }), + menuList: (base: any) => ({ ...base, zIndex: 9999 }) + }} + /> + ); +} + /** * Detail view for a single Part instance */ @@ -146,6 +187,95 @@ export default function PartDetail() { refetchOnMount: true }); + // Fetch information on part revision + const partRevisionQuery = useQuery({ + refetchOnMount: true, + queryKey: [ + 'part_revisions', + part.pk, + part.revision_of, + part.revision_count + ], + queryFn: async () => { + if (!part.revision_of && !part.revision_count) { + return []; + } + + const revisions = []; + + // First, fetch information for the top-level part + if (part.revision_of) { + await api + .get(apiUrl(ApiEndpoints.part_list, part.revision_of)) + .then((response) => { + revisions.push(response.data); + }); + } else { + revisions.push(part); + } + + const url = apiUrl(ApiEndpoints.part_list); + + await api + .get(url, { + params: { + revision_of: part.revision_of || part.pk + } + }) + .then((response) => { + switch (response.status) { + case 200: + response.data.forEach((r: any) => { + revisions.push(r); + }); + break; + default: + break; + } + }); + + return revisions; + } + }); + + const partRevisionOptions: any[] = useMemo(() => { + if (partRevisionQuery.isFetching || !partRevisionQuery.data) { + return []; + } + + if (!part.revision_of && !part.revision_count) { + return []; + } + + const options: any[] = partRevisionQuery.data.map((revision: any) => { + return { + value: revision.pk, + label: revision.full_name, + part: revision + }; + }); + + // Add this part if not already available + if (!options.find((o) => o.value == part.pk)) { + options.push({ + value: part.pk, + label: part.full_name, + part: part + }); + } + + return options.sort((a, b) => { + return `${a.part.revision}`.localeCompare(b.part.revision); + }); + }, [part, partRevisionQuery.isFetching, partRevisionQuery.data]); + + const enableRevisionSelection: boolean = useMemo(() => { + return ( + partRevisionOptions.length > 0 && + globalSettings.isSet('PART_ENABLE_REVISION') + ); + }, [partRevisionOptions, globalSettings]); + const detailsPanel = useMemo(() => { if (instanceQuery.isFetching) { return ; @@ -481,24 +611,32 @@ export default function PartDetail() { return part ? ( - - - - - - + + + + + + + + {enableRevisionSelection && ( + + {t`Select Part Revision`} + + + )} + @@ -513,6 +651,8 @@ export default function PartDetail() { serials, instanceQuery.isFetching, instanceQuery.data, + enableRevisionSelection, + partRevisionOptions, partRequirementsQuery.isFetching, partRequirements ]); @@ -680,88 +820,6 @@ export default function PartDetail() { ]; }, [id, part, user, globalSettings, userSettings, detailsPanel]); - // Fetch information on part revision - const partRevisionQuery = useQuery({ - refetchOnMount: true, - queryKey: [ - 'part_revisions', - part.pk, - part.revision_of, - part.revision_count - ], - queryFn: async () => { - if (!part.revision_of && !part.revision_count) { - return []; - } - - const revisions = []; - - // First, fetch information for the top-level part - if (part.revision_of) { - await api - .get(apiUrl(ApiEndpoints.part_list, part.revision_of)) - .then((response) => { - revisions.push(response.data); - }); - } else { - revisions.push(part); - } - - const url = apiUrl(ApiEndpoints.part_list); - - await api - .get(url, { - params: { - revision_of: part.revision_of || part.pk - } - }) - .then((response) => { - switch (response.status) { - case 200: - response.data.forEach((r: any) => { - revisions.push(r); - }); - break; - default: - break; - } - }); - - return revisions; - } - }); - - const partRevisionOptions: any[] = useMemo(() => { - if (partRevisionQuery.isFetching || !partRevisionQuery.data) { - return []; - } - - if (!part.revision_of && !part.revision_count) { - return []; - } - - const options: any[] = partRevisionQuery.data.map((revision: any) => { - return { - value: revision.pk, - label: revision.full_name, - part: revision - }; - }); - - // Add this part if not already available - if (!options.find((o) => o.value == part.pk)) { - options.push({ - value: part.pk, - label: part.full_name, - part: part - }); - } - - return options.sort((a, b) => { - return `${a.part.revision}`.localeCompare(b.part.revision); - }); - }, [part, partRevisionQuery.isFetching, partRevisionQuery.data]); - const breadcrumbs = useMemo(() => { return [ { name: t`Parts`, url: '/part' }, @@ -1003,13 +1061,6 @@ export default function PartDetail() { ]; }, [id, part, user, stockAdjustActions.menuActions]); - const enableRevisionSelection: boolean = useMemo(() => { - return ( - partRevisionOptions.length > 0 && - globalSettings.isSet('PART_ENABLE_REVISION') - ); - }, [partRevisionOptions, globalSettings]); - return ( <> {editPart.modal} @@ -1059,38 +1110,6 @@ export default function PartDetail() { editAction={editPart.open} editEnabled={user.hasChangeRole(UserRoles.part)} actions={partActions} - detail={ - enableRevisionSelection ? ( - - {t`Select Part Revision`} - {title ?? t`Table Filters`}} > + + {hasFilters && filterSet.activeFilters?.map((f) => ( ))} - {hasFilters && } {addFilter && (