diff --git a/src/frontend/src/components/calendar/Calendar.tsx b/src/frontend/src/components/calendar/Calendar.tsx index 8b80347fce..dcddcdcfd1 100644 --- a/src/frontend/src/components/calendar/Calendar.tsx +++ b/src/frontend/src/components/calendar/Calendar.tsx @@ -1,4 +1,8 @@ -import type { CalendarOptions, DatesSetArg } from '@fullcalendar/core'; +import type { + CalendarOptions, + DatesSetArg, + EventContentArg +} from '@fullcalendar/core'; import allLocales from '@fullcalendar/core/locales-all'; import dayGridPlugin from '@fullcalendar/daygrid'; import interactionPlugin from '@fullcalendar/interaction'; @@ -15,6 +19,7 @@ import { Box, Button, Group, + HoverCard, Indicator, LoadingOverlay, Popover, @@ -29,7 +34,13 @@ import { IconDownload, IconFilter } from '@tabler/icons-react'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { + type ReactNode, + useCallback, + useEffect, + useMemo, + useState +} from 'react'; import { useShallow } from 'zustand/react/shallow'; import { defaultLocale, @@ -44,6 +55,7 @@ export interface InvenTreeCalendarProps extends CalendarOptions { enableDownload?: boolean; enableFilters?: boolean; enableSearch?: boolean; + eventTooltipContent?: (event: EventContentArg) => ReactNode; filters?: TableFilter[]; isLoading?: boolean; state: CalendarState; @@ -53,6 +65,7 @@ export default function Calendar({ enableDownload, enableFilters = false, enableSearch, + eventTooltipContent, isLoading, filters, state, @@ -112,6 +125,36 @@ export default function Calendar({ [calendarProps.datesSet, state.ref, state.setMonthName] ); + const wrappedEventContent = useCallback( + (arg: EventContentArg) => { + const inner = + typeof calendarProps.eventContent === 'function' + ? calendarProps.eventContent(arg, null) + : (calendarProps.eventContent ?? null); + + if (!eventTooltipContent) return inner; + + const tooltip = eventTooltipContent(arg); + + if (!tooltip) return inner; + + return ( + + +
{inner}
+
+ {tooltip} +
+ ); + }, + [calendarProps.eventContent, eventTooltipContent] + ); + return ( <> {state.exportModal.modal} @@ -217,6 +260,7 @@ export default function Calendar({ footerToolbar={false} {...calendarProps} datesSet={datesSet} + eventContent={wrappedEventContent} /> diff --git a/src/frontend/src/components/calendar/OrderCalendar.tsx b/src/frontend/src/components/calendar/OrderCalendar.tsx index 9c3523653e..95fd76abe3 100644 --- a/src/frontend/src/components/calendar/OrderCalendar.tsx +++ b/src/frontend/src/components/calendar/OrderCalendar.tsx @@ -46,12 +46,14 @@ export default function OrderCalendar({ model, role, params, - filters + filters, + tooltip }: { model: ModelType; role: UserRoles; params: Record; filters?: TableFilter[]; + tooltip?: (event: EventContentArg) => React.ReactNode; }) { const navigate = useNavigate(); const user = useUserState(); @@ -106,6 +108,7 @@ export default function OrderCalendar({ const end: string = order.target_date || start; return { + order: order, id: order.pk, title: order.reference, description: order.description, @@ -209,6 +212,7 @@ export default function OrderCalendar({ state={calendarState} filters={calendarFilters} editable={true} + eventTooltipContent={tooltip} eventContent={renderOrder} eventClick={onClickOrder} eventChange={onEditOrder} diff --git a/src/frontend/src/components/calendar/OrderCalendarToolTip.tsx b/src/frontend/src/components/calendar/OrderCalendarToolTip.tsx new file mode 100644 index 0000000000..dba5259358 --- /dev/null +++ b/src/frontend/src/components/calendar/OrderCalendarToolTip.tsx @@ -0,0 +1,61 @@ +import type { EventContentArg } from '@fullcalendar/core'; +import type { ModelType } from '@lib/enums/ModelType'; +import { resolveItem } from '@lib/functions/Conversion'; +import { t } from '@lingui/core/macro'; +import { Badge, Divider, Group, Stack, Text } from '@mantine/core'; +import { formatDate } from '../../defaults/formatters'; +import { RenderInstance } from '../render/Instance'; +import { RenderOwner } from '../render/User'; + +export default function OrderCalendarToolTip({ + event, + instanceLookup, + modelType +}: { + event: EventContentArg; + instanceLookup: string; + modelType: ModelType; +}) { + // Extract the order instance from the event + const order = event?.event?._def?.extendedProps?.order; + + const instance = resolveItem(order, instanceLookup); + + if (!order) return null; + + return ( + + + + + + {order.reference} + + {order.description || order.title} + + {order.start_date && ( + + {t`Start Date`} + {formatDate(order.start_date)} + + )} + {order.target_date && ( + + {t`Target Date`} + {formatDate(order.target_date)} + {order.overdue && ( + + {t`Overdue`} + + )} + + )} + {order.responsible && ( + + {t`Responsible`} + + + )} + + ); +} diff --git a/src/frontend/src/components/render/Company.tsx b/src/frontend/src/components/render/Company.tsx index e8050d5f12..09aeee04cc 100644 --- a/src/frontend/src/components/render/Company.tsx +++ b/src/frontend/src/components/render/Company.tsx @@ -50,11 +50,11 @@ export function RenderCompany( return ( ); diff --git a/src/frontend/src/pages/build/BuildIndex.tsx b/src/frontend/src/pages/build/BuildIndex.tsx index 287b1d9cc4..f52441923d 100644 --- a/src/frontend/src/pages/build/BuildIndex.tsx +++ b/src/frontend/src/pages/build/BuildIndex.tsx @@ -6,14 +6,16 @@ import { IconTable, IconTools } from '@tabler/icons-react'; -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; +import type { EventContentArg } from '@fullcalendar/core'; import { ModelType, PluginPanelKey } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import type { TableFilter } from '@lib/types/Filters'; import type { PanelType } from '@lib/types/Panel'; import { useLocalStorage } from '@mantine/hooks'; import OrderCalendar from '../../components/calendar/OrderCalendar'; +import OrderCalendarToolTip from '../../components/calendar/OrderCalendarToolTip'; import PermissionDenied from '../../components/errors/PermissionDenied'; import { PageDetail } from '../../components/nav/PageDetail'; import { PanelGroup } from '../../components/panels/PanelGroup'; @@ -34,12 +36,21 @@ function BuildOrderCalendar() { }); }, [globalSettings]); + const renderTooltip = useCallback((event: EventContentArg) => { + return OrderCalendarToolTip({ + event: event, + modelType: ModelType.part, + instanceLookup: 'part_detail' + }); + }, []); + return ( ); } diff --git a/src/frontend/src/pages/purchasing/PurchasingIndex.tsx b/src/frontend/src/pages/purchasing/PurchasingIndex.tsx index 3087c9b530..bdaa271c0e 100644 --- a/src/frontend/src/pages/purchasing/PurchasingIndex.tsx +++ b/src/frontend/src/pages/purchasing/PurchasingIndex.tsx @@ -10,13 +10,15 @@ import { IconShoppingCart, IconTable } from '@tabler/icons-react'; -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; +import type { EventContentArg } from '@fullcalendar/core'; import { ModelType, PluginPanelKey } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import type { TableFilter } from '@lib/index'; import { useLocalStorage } from '@mantine/hooks'; import OrderCalendar from '../../components/calendar/OrderCalendar'; +import OrderCalendarToolTip from '../../components/calendar/OrderCalendarToolTip'; import PermissionDenied from '../../components/errors/PermissionDenied'; import { PageDetail } from '../../components/nav/PageDetail'; import { PanelGroup } from '../../components/panels/PanelGroup'; @@ -37,12 +39,21 @@ function PurchaseOrderCalendar() { return PurchaseOrderFilters({ includeDateFilters: false }); }, []); + const renderTooltip = useCallback((event: EventContentArg) => { + return OrderCalendarToolTip({ + event: event, + modelType: ModelType.company, + instanceLookup: 'supplier_detail' + }); + }, []); + return ( ); } diff --git a/src/frontend/src/pages/sales/SalesIndex.tsx b/src/frontend/src/pages/sales/SalesIndex.tsx index a36fde83da..0f1c0879b0 100644 --- a/src/frontend/src/pages/sales/SalesIndex.tsx +++ b/src/frontend/src/pages/sales/SalesIndex.tsx @@ -9,13 +9,15 @@ import { IconTruckDelivery, IconTruckReturn } from '@tabler/icons-react'; -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; +import type { EventContentArg } from '@fullcalendar/core'; import { ModelType, PluginPanelKey } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import type { TableFilter } from '@lib/index'; import { useLocalStorage } from '@mantine/hooks'; import OrderCalendar from '../../components/calendar/OrderCalendar'; +import OrderCalendarToolTip from '../../components/calendar/OrderCalendarToolTip'; import PermissionDenied from '../../components/errors/PermissionDenied'; import { PageDetail } from '../../components/nav/PageDetail'; import { PanelGroup } from '../../components/panels/PanelGroup'; @@ -35,12 +37,21 @@ function SalesOrderCalendar() { return SalesOrderFilters({ includeDateFilters: false }); }, []); + const renderTooltip = useCallback((event: EventContentArg) => { + return OrderCalendarToolTip({ + event: event, + modelType: ModelType.company, + instanceLookup: 'customer_detail' + }); + }, []); + return ( ); } @@ -50,12 +61,21 @@ const ReturnOrderCalendar = () => { return SalesOrderFilters({ includeDateFilters: false }); }, []); + const renderTooltip = useCallback((event: EventContentArg) => { + return OrderCalendarToolTip({ + event: event, + modelType: ModelType.company, + instanceLookup: 'customer_detail' + }); + }, []); + return ( ); }; diff --git a/src/frontend/src/tables/InvenTreeTableHeader.tsx b/src/frontend/src/tables/InvenTreeTableHeader.tsx index 612b6ba06d..2defcb7a22 100644 --- a/src/frontend/src/tables/InvenTreeTableHeader.tsx +++ b/src/frontend/src/tables/InvenTreeTableHeader.tsx @@ -276,6 +276,7 @@ export default function InvenTreeTableHeader({ {tableState.filterSet.activeFilters?.map((filter) => (