mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 15:15:42 +00:00 
			
		
		
		
	Merge branch 'master' of https://github.com/inventree/InvenTree into pui-maintine-v7
This commit is contained in:
		@@ -90,7 +90,7 @@ function detect_envs() {
 | 
				
			|||||||
    echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
 | 
					    echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Install parser
 | 
					    # Install parser
 | 
				
			||||||
    pip install -r ${APP_HOME}/.github/requirements.txt -q
 | 
					    pip install --require-hashes -r ${APP_HOME}/.github/requirements.txt -q
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Load config
 | 
					    # Load config
 | 
				
			||||||
    local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
 | 
					    local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,14 @@
 | 
				
			|||||||
"""InvenTree API version information."""
 | 
					"""InvenTree API version information."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# InvenTree API version
 | 
					# InvenTree API version
 | 
				
			||||||
INVENTREE_API_VERSION = 192
 | 
					INVENTREE_API_VERSION = 193
 | 
				
			||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
 | 
					"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
INVENTREE_API_TEXT = """
 | 
					INVENTREE_API_TEXT = """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					v193 - 2024-04-30 : https://github.com/inventree/InvenTree/pull/7144
 | 
				
			||||||
 | 
					    - Adds "assigned_to" filter to PurchaseOrder / SalesOrder / ReturnOrder API endpoints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
v192 - 2024-04-23 : https://github.com/inventree/InvenTree/pull/7106
 | 
					v192 - 2024-04-23 : https://github.com/inventree/InvenTree/pull/7106
 | 
				
			||||||
    - Adds 'trackable' ordering option to BuildLineLabel API endpoint
 | 
					    - Adds 'trackable' ordering option to BuildLineLabel API endpoint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -148,6 +148,10 @@ class OrderFilter(rest_filters.FilterSet):
 | 
				
			|||||||
            return queryset.exclude(project_code=None)
 | 
					            return queryset.exclude(project_code=None)
 | 
				
			||||||
        return queryset.filter(project_code=None)
 | 
					        return queryset.filter(project_code=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assigned_to = rest_filters.ModelChoiceFilter(
 | 
				
			||||||
 | 
					        queryset=Owner.objects.all(), field_name='responsible'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LineItemFilter(rest_filters.FilterSet):
 | 
					class LineItemFilter(rest_filters.FilterSet):
 | 
				
			||||||
    """Base class for custom API filters for order line item list(s)."""
 | 
					    """Base class for custom API filters for order line item list(s)."""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -77,16 +77,18 @@ class AbstractOrderSerializer(serializers.Serializer):
 | 
				
			|||||||
    """Abstract serializer class which provides fields common to all order types."""
 | 
					    """Abstract serializer class which provides fields common to all order types."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Number of line items in this order
 | 
					    # Number of line items in this order
 | 
				
			||||||
    line_items = serializers.IntegerField(read_only=True)
 | 
					    line_items = serializers.IntegerField(read_only=True, label=_('Line Items'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Number of completed line items (this is an annotated field)
 | 
					    # Number of completed line items (this is an annotated field)
 | 
				
			||||||
    completed_lines = serializers.IntegerField(read_only=True)
 | 
					    completed_lines = serializers.IntegerField(
 | 
				
			||||||
 | 
					        read_only=True, label=_('Completed Lines')
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Human-readable status text (read-only)
 | 
					    # Human-readable status text (read-only)
 | 
				
			||||||
    status_text = serializers.CharField(source='get_status_display', read_only=True)
 | 
					    status_text = serializers.CharField(source='get_status_display', read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # status field cannot be set directly
 | 
					    # status field cannot be set directly
 | 
				
			||||||
    status = serializers.IntegerField(read_only=True)
 | 
					    status = serializers.IntegerField(read_only=True, label=_('Order Status'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Reference string is *required*
 | 
					    # Reference string is *required*
 | 
				
			||||||
    reference = serializers.CharField(required=True)
 | 
					    reference = serializers.CharField(required=True)
 | 
				
			||||||
@@ -114,7 +116,9 @@ class AbstractOrderSerializer(serializers.Serializer):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    barcode_hash = serializers.CharField(read_only=True)
 | 
					    barcode_hash = serializers.CharField(read_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    creation_date = serializers.DateField(required=False, allow_null=True)
 | 
					    creation_date = serializers.DateField(
 | 
				
			||||||
 | 
					        required=False, allow_null=True, label=_('Creation Date')
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validate_reference(self, reference):
 | 
					    def validate_reference(self, reference):
 | 
				
			||||||
        """Custom validation for the reference field."""
 | 
					        """Custom validation for the reference field."""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,6 @@ import { ActionButton, ActionButtonProps } from './ActionButton';
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * A generic icon button which is used to add or create a new item
 | 
					 * A generic icon button which is used to add or create a new item
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function AddItemButton(props: ActionButtonProps) {
 | 
					export function AddItemButton(props: Readonly<ActionButtonProps>) {
 | 
				
			||||||
  return <ActionButton {...props} color="green" icon={<IconPlus />} />;
 | 
					  return <ActionButton {...props} color="green" icon={<IconPlus />} />;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,7 +36,7 @@ export function SplitButton({
 | 
				
			|||||||
  selected,
 | 
					  selected,
 | 
				
			||||||
  setSelected,
 | 
					  setSelected,
 | 
				
			||||||
  loading
 | 
					  loading
 | 
				
			||||||
}: SplitButtonProps) {
 | 
					}: Readonly<SplitButtonProps>) {
 | 
				
			||||||
  const [current, setCurrent] = useState<string>(defaultSelected);
 | 
					  const [current, setCurrent] = useState<string>(defaultSelected);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -180,7 +180,7 @@ function NameBadge({ pk, type }: { pk: string | number; type: BadgeType }) {
 | 
				
			|||||||
 * If owner is defined, only renders a badge
 | 
					 * If owner is defined, only renders a badge
 | 
				
			||||||
 * If user is defined, a badge is rendered in addition to main value
 | 
					 * If user is defined, a badge is rendered in addition to main value
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function TableStringValue(props: FieldProps) {
 | 
					function TableStringValue(props: Readonly<FieldProps>) {
 | 
				
			||||||
  let value = props?.field_value;
 | 
					  let value = props?.field_value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (value === undefined) {
 | 
					  if (value === undefined) {
 | 
				
			||||||
@@ -217,11 +217,11 @@ function TableStringValue(props: FieldProps) {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function BooleanValue(props: FieldProps) {
 | 
					function BooleanValue(props: Readonly<FieldProps>) {
 | 
				
			||||||
  return <YesNoButton value={props.field_value} />;
 | 
					  return <YesNoButton value={props.field_value} />;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function TableAnchorValue(props: FieldProps) {
 | 
					function TableAnchorValue(props: Readonly<FieldProps>) {
 | 
				
			||||||
  if (props.field_data.external) {
 | 
					  if (props.field_data.external) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <Anchor
 | 
					      <Anchor
 | 
				
			||||||
@@ -303,7 +303,7 @@ function TableAnchorValue(props: FieldProps) {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function ProgressBarValue(props: FieldProps) {
 | 
					function ProgressBarValue(props: Readonly<FieldProps>) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <ProgressBar
 | 
					    <ProgressBar
 | 
				
			||||||
      value={props.field_data.progress}
 | 
					      value={props.field_data.progress}
 | 
				
			||||||
@@ -313,7 +313,7 @@ function ProgressBarValue(props: FieldProps) {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function StatusValue(props: FieldProps) {
 | 
					function StatusValue(props: Readonly<FieldProps>) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <StatusRenderer type={props.field_data.model} status={props.field_value} />
 | 
					    <StatusRenderer type={props.field_data.model} status={props.field_value} />
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ export type DetailsBadgeProps = {
 | 
				
			|||||||
  visible?: boolean;
 | 
					  visible?: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function DetailsBadge(props: DetailsBadgeProps) {
 | 
					export default function DetailsBadge(props: Readonly<DetailsBadgeProps>) {
 | 
				
			||||||
  if (props.visible == false) {
 | 
					  if (props.visible == false) {
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -322,7 +322,7 @@ function ImageActionButtons({
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Renders an image with action buttons for display on Details panels
 | 
					 * Renders an image with action buttons for display on Details panels
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function DetailsImage(props: DetailImageProps) {
 | 
					export function DetailsImage(props: Readonly<DetailImageProps>) {
 | 
				
			||||||
  // Displays a group of ActionButtons on hover
 | 
					  // Displays a group of ActionButtons on hover
 | 
				
			||||||
  const { hovered, ref } = useHover();
 | 
					  const { hovered, ref } = useHover();
 | 
				
			||||||
  const [img, setImg] = useState<string>(props.src ?? backup_image);
 | 
					  const [img, setImg] = useState<string>(props.src ?? backup_image);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,7 +87,7 @@ type TemplateEditorProps = {
 | 
				
			|||||||
  template: TemplateI;
 | 
					  template: TemplateI;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function TemplateEditor(props: TemplateEditorProps) {
 | 
					export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
 | 
				
			||||||
  const { downloadUrl, editors, previewAreas, preview } = props;
 | 
					  const { downloadUrl, editors, previewAreas, preview } = props;
 | 
				
			||||||
  const editorRef = useRef<EditorRef>();
 | 
					  const editorRef = useRef<EditorRef>();
 | 
				
			||||||
  const previewRef = useRef<PreviewAreaRef>();
 | 
					  const previewRef = useRef<PreviewAreaRef>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,12 @@ interface DocInfoProps extends BaseDocProps {
 | 
				
			|||||||
  size?: number;
 | 
					  size?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DocInfo({ size = 18, text, detail, link }: DocInfoProps) {
 | 
					export function DocInfo({
 | 
				
			||||||
 | 
					  size = 18,
 | 
				
			||||||
 | 
					  text,
 | 
				
			||||||
 | 
					  detail,
 | 
				
			||||||
 | 
					  link
 | 
				
			||||||
 | 
					}: Readonly<DocInfoProps>) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <DocTooltip text={text} detail={detail} link={link}>
 | 
					    <DocTooltip text={text} detail={detail} link={link}>
 | 
				
			||||||
      <IconInfoCircle size={size} />
 | 
					      <IconInfoCircle size={size} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ export function DocTooltip({
 | 
				
			|||||||
  detail,
 | 
					  detail,
 | 
				
			||||||
  link,
 | 
					  link,
 | 
				
			||||||
  docchildren
 | 
					  docchildren
 | 
				
			||||||
}: DocTooltipProps) {
 | 
					}: Readonly<DocTooltipProps>) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <HoverCard
 | 
					    <HoverCard
 | 
				
			||||||
      shadow="md"
 | 
					      shadow="md"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,14 @@ export type ProgressBarProps = {
 | 
				
			|||||||
  maximum?: number;
 | 
					  maximum?: number;
 | 
				
			||||||
  label?: string;
 | 
					  label?: string;
 | 
				
			||||||
  progressLabel?: boolean;
 | 
					  progressLabel?: boolean;
 | 
				
			||||||
 | 
					  size?: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * A progress bar element, built on mantine.Progress
 | 
					 * A progress bar element, built on mantine.Progress
 | 
				
			||||||
 * The color of the bar is determined based on the value
 | 
					 * The color of the bar is determined based on the value
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function ProgressBar(props: ProgressBarProps) {
 | 
					export function ProgressBar(props: Readonly<ProgressBarProps>) {
 | 
				
			||||||
  const progress = useMemo(() => {
 | 
					  const progress = useMemo(() => {
 | 
				
			||||||
    let maximum = props.maximum ?? 100;
 | 
					    let maximum = props.maximum ?? 100;
 | 
				
			||||||
    let value = Math.max(props.value, 0);
 | 
					    let value = Math.max(props.value, 0);
 | 
				
			||||||
@@ -31,8 +32,8 @@ export function ProgressBar(props: ProgressBarProps) {
 | 
				
			|||||||
      <Progress
 | 
					      <Progress
 | 
				
			||||||
        value={progress}
 | 
					        value={progress}
 | 
				
			||||||
        color={progress < 100 ? 'orange' : progress > 100 ? 'blue' : 'green'}
 | 
					        color={progress < 100 ? 'orange' : progress > 100 ? 'blue' : 'green'}
 | 
				
			||||||
        size="sm"
 | 
					        size={props.size ?? 'md'}
 | 
				
			||||||
        radius="xs"
 | 
					        radius="sm"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </Stack>
 | 
					    </Stack>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ export function TitleWithDoc({
 | 
				
			|||||||
  size,
 | 
					  size,
 | 
				
			||||||
  text,
 | 
					  text,
 | 
				
			||||||
  detail
 | 
					  detail
 | 
				
			||||||
}: DocTitleProps) {
 | 
					}: Readonly<DocTitleProps>) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Group>
 | 
					    <Group>
 | 
				
			||||||
      <Title variant={variant} order={order} size={size}>
 | 
					      <Title variant={variant} order={order} size={size}>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,7 +29,7 @@ function DetailDrawerComponent({
 | 
				
			|||||||
  size,
 | 
					  size,
 | 
				
			||||||
  closeOnEscape = true,
 | 
					  closeOnEscape = true,
 | 
				
			||||||
  renderContent
 | 
					  renderContent
 | 
				
			||||||
}: DrawerProps) {
 | 
					}: Readonly<DrawerProps>) {
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
  const { id } = useParams();
 | 
					  const { id } = useParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,7 +80,7 @@ function DetailDrawerComponent({
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DetailDrawer(props: DrawerProps) {
 | 
					export function DetailDrawer(props: Readonly<DrawerProps>) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Routes>
 | 
					    <Routes>
 | 
				
			||||||
      <Route path=":id?/" element={<DetailDrawerComponent {...props} />} />
 | 
					      <Route path=":id?/" element={<DetailDrawerComponent {...props} />} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,17 @@ import { ApiImage } from '../images/ApiImage';
 | 
				
			|||||||
import { StylishText } from '../items/StylishText';
 | 
					import { StylishText } from '../items/StylishText';
 | 
				
			||||||
import { Breadcrumb, BreadcrumbList } from './BreadcrumbList';
 | 
					import { Breadcrumb, BreadcrumbList } from './BreadcrumbList';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface PageDetailInterface {
 | 
				
			||||||
 | 
					  title?: string;
 | 
				
			||||||
 | 
					  subtitle?: string;
 | 
				
			||||||
 | 
					  imageUrl?: string;
 | 
				
			||||||
 | 
					  detail?: ReactNode;
 | 
				
			||||||
 | 
					  badges?: ReactNode[];
 | 
				
			||||||
 | 
					  breadcrumbs?: Breadcrumb[];
 | 
				
			||||||
 | 
					  breadcrumbAction?: () => void;
 | 
				
			||||||
 | 
					  actions?: ReactNode[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Construct a "standard" page detail for common display between pages.
 | 
					 * Construct a "standard" page detail for common display between pages.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -20,16 +31,7 @@ export function PageDetail({
 | 
				
			|||||||
  breadcrumbs,
 | 
					  breadcrumbs,
 | 
				
			||||||
  breadcrumbAction,
 | 
					  breadcrumbAction,
 | 
				
			||||||
  actions
 | 
					  actions
 | 
				
			||||||
}: {
 | 
					}: Readonly<PageDetailInterface>) {
 | 
				
			||||||
  title?: string;
 | 
					 | 
				
			||||||
  subtitle?: string;
 | 
					 | 
				
			||||||
  imageUrl?: string;
 | 
					 | 
				
			||||||
  detail?: ReactNode;
 | 
					 | 
				
			||||||
  badges?: ReactNode[];
 | 
					 | 
				
			||||||
  breadcrumbs?: Breadcrumb[];
 | 
					 | 
				
			||||||
  breadcrumbAction?: () => void;
 | 
					 | 
				
			||||||
  actions?: ReactNode[];
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Stack gap="xs">
 | 
					    <Stack gap="xs">
 | 
				
			||||||
      {breadcrumbs && breadcrumbs.length > 0 && (
 | 
					      {breadcrumbs && breadcrumbs.length > 0 && (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,7 +50,7 @@ function BasePanelGroup({
 | 
				
			|||||||
  onPanelChange,
 | 
					  onPanelChange,
 | 
				
			||||||
  selectedPanel,
 | 
					  selectedPanel,
 | 
				
			||||||
  collapsible = true
 | 
					  collapsible = true
 | 
				
			||||||
}: PanelProps): ReactNode {
 | 
					}: Readonly<PanelProps>): ReactNode {
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
  const { panel } = useParams();
 | 
					  const { panel } = useParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -178,7 +178,11 @@ function BasePanelGroup({
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function IndexPanelComponent({ pageKey, selectedPanel, panels }: PanelProps) {
 | 
					function IndexPanelComponent({
 | 
				
			||||||
 | 
					  pageKey,
 | 
				
			||||||
 | 
					  selectedPanel,
 | 
				
			||||||
 | 
					  panels
 | 
				
			||||||
 | 
					}: Readonly<PanelProps>) {
 | 
				
			||||||
  const lastUsedPanel = useLocalState((state) => {
 | 
					  const lastUsedPanel = useLocalState((state) => {
 | 
				
			||||||
    const panelName =
 | 
					    const panelName =
 | 
				
			||||||
      selectedPanel || state.lastUsedPanels[pageKey] || panels[0]?.name;
 | 
					      selectedPanel || state.lastUsedPanels[pageKey] || panels[0]?.name;
 | 
				
			||||||
@@ -203,7 +207,7 @@ function IndexPanelComponent({ pageKey, selectedPanel, panels }: PanelProps) {
 | 
				
			|||||||
 * @param onPanelChange - Callback when the active panel changes
 | 
					 * @param onPanelChange - Callback when the active panel changes
 | 
				
			||||||
 * @param collapsible - If true, the panel group can be collapsed (defaults to true)
 | 
					 * @param collapsible - If true, the panel group can be collapsed (defaults to true)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function PanelGroup(props: PanelProps) {
 | 
					export function PanelGroup(props: Readonly<PanelProps>) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Routes>
 | 
					    <Routes>
 | 
				
			||||||
      <Route index element={<IndexPanelComponent {...props} />} />
 | 
					      <Route index element={<IndexPanelComponent {...props} />} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,15 @@ import { IconSwitch } from '@tabler/icons-react';
 | 
				
			|||||||
import { ReactNode } from 'react';
 | 
					import { ReactNode } from 'react';
 | 
				
			||||||
import { Link } from 'react-router-dom';
 | 
					import { Link } from 'react-router-dom';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface SettingsHeaderInterface {
 | 
				
			||||||
 | 
					  title: string | ReactNode;
 | 
				
			||||||
 | 
					  shorthand?: string;
 | 
				
			||||||
 | 
					  subtitle?: string | ReactNode;
 | 
				
			||||||
 | 
					  switch_condition?: boolean;
 | 
				
			||||||
 | 
					  switch_text?: string | ReactNode;
 | 
				
			||||||
 | 
					  switch_link?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Construct a settings page header with interlinks to one other settings page
 | 
					 * Construct a settings page header with interlinks to one other settings page
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@@ -13,14 +22,7 @@ export function SettingsHeader({
 | 
				
			|||||||
  switch_condition = true,
 | 
					  switch_condition = true,
 | 
				
			||||||
  switch_text,
 | 
					  switch_text,
 | 
				
			||||||
  switch_link
 | 
					  switch_link
 | 
				
			||||||
}: {
 | 
					}: Readonly<SettingsHeaderInterface>) {
 | 
				
			||||||
  title: string | ReactNode;
 | 
					 | 
				
			||||||
  shorthand?: string;
 | 
					 | 
				
			||||||
  subtitle?: string | ReactNode;
 | 
					 | 
				
			||||||
  switch_condition?: boolean;
 | 
					 | 
				
			||||||
  switch_text?: string | ReactNode;
 | 
					 | 
				
			||||||
  switch_link?: string;
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Stack gap="0" ml={'sm'}>
 | 
					    <Stack gap="0" ml={'sm'}>
 | 
				
			||||||
      <Group>
 | 
					      <Group>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,15 @@
 | 
				
			|||||||
import { ReactNode } from 'react';
 | 
					import { ReactNode } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
import { RenderInlineModel } from './Instance';
 | 
					import { InstanceRenderInterface, RenderInlineModel } from './Instance';
 | 
				
			||||||
import { StatusRenderer } from './StatusRenderer';
 | 
					import { StatusRenderer } from './StatusRenderer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inline rendering of a single BuildOrder instance
 | 
					 * Inline rendering of a single BuildOrder instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderBuildOrder({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderBuildOrder({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <RenderInlineModel
 | 
					    <RenderInlineModel
 | 
				
			||||||
      primary={instance.reference}
 | 
					      primary={instance.reference}
 | 
				
			||||||
@@ -24,7 +26,9 @@ export function RenderBuildOrder({ instance }: { instance: any }): ReactNode {
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
 * Inline rendering of a single BuildLine instance
 | 
					 * Inline rendering of a single BuildLine instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderBuildLine({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderBuildLine({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <RenderInlineModel
 | 
					    <RenderInlineModel
 | 
				
			||||||
      primary={instance.part_detail.full_name}
 | 
					      primary={instance.part_detail.full_name}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,13 @@
 | 
				
			|||||||
import { ReactNode } from 'react';
 | 
					import { ReactNode } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { RenderInlineModel } from './Instance';
 | 
					import { InstanceRenderInterface, RenderInlineModel } from './Instance';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inline rendering of a single Address instance
 | 
					 * Inline rendering of a single Address instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderAddress({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderAddress({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  let text = [
 | 
					  let text = [
 | 
				
			||||||
    instance.country,
 | 
					    instance.country,
 | 
				
			||||||
    instance.postal_code,
 | 
					    instance.postal_code,
 | 
				
			||||||
@@ -23,7 +25,9 @@ export function RenderAddress({ instance }: { instance: any }): ReactNode {
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inline rendering of a single Company instance
 | 
					 * Inline rendering of a single Company instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderCompany({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderCompany({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  // TODO: Handle URL
 | 
					  // TODO: Handle URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@@ -38,14 +42,18 @@ export function RenderCompany({ instance }: { instance: any }): ReactNode {
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inline rendering of a single Contact instance
 | 
					 * Inline rendering of a single Contact instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderContact({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderContact({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  return <RenderInlineModel primary={instance.name} />;
 | 
					  return <RenderInlineModel primary={instance.name} />;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inline rendering of a single SupplierPart instance
 | 
					 * Inline rendering of a single SupplierPart instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderSupplierPart({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  // TODO: handle URL
 | 
					  // TODO: handle URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let supplier = instance.supplier_detail ?? {};
 | 
					  let supplier = instance.supplier_detail ?? {};
 | 
				
			||||||
@@ -66,9 +74,7 @@ export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderManufacturerPart({
 | 
					export function RenderManufacturerPart({
 | 
				
			||||||
  instance
 | 
					  instance
 | 
				
			||||||
}: {
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  instance: any;
 | 
					 | 
				
			||||||
}): ReactNode {
 | 
					 | 
				
			||||||
  let part = instance.part_detail ?? {};
 | 
					  let part = instance.part_detail ?? {};
 | 
				
			||||||
  let manufacturer = instance.manufacturer_detail ?? {};
 | 
					  let manufacturer = instance.manufacturer_detail ?? {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,10 @@
 | 
				
			|||||||
import { ReactNode } from 'react';
 | 
					import { ReactNode } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { RenderInlineModel } from './Instance';
 | 
					import { InstanceRenderInterface, RenderInlineModel } from './Instance';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RenderProjectCode({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderProjectCode({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    instance && (
 | 
					    instance && (
 | 
				
			||||||
      <RenderInlineModel
 | 
					      <RenderInlineModel
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@ type EnumDictionary<T extends string | symbol | number, U> = {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
const RendererLookup: EnumDictionary<
 | 
					const RendererLookup: EnumDictionary<
 | 
				
			||||||
  ModelType,
 | 
					  ModelType,
 | 
				
			||||||
  (props: { instance: any }) => ReactNode
 | 
					  (props: Readonly<InstanceRenderInterface>) => ReactNode
 | 
				
			||||||
> = {
 | 
					> = {
 | 
				
			||||||
  [ModelType.address]: RenderAddress,
 | 
					  [ModelType.address]: RenderAddress,
 | 
				
			||||||
  [ModelType.build]: RenderBuildOrder,
 | 
					  [ModelType.build]: RenderBuildOrder,
 | 
				
			||||||
@@ -139,3 +139,7 @@ export function UnknownRenderer({
 | 
				
			|||||||
    </Alert>
 | 
					    </Alert>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface InstanceRenderInterface {
 | 
				
			||||||
 | 
					  instance: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import { t } from '@lingui/macro';
 | 
				
			|||||||
import { ReactNode } from 'react';
 | 
					import { ReactNode } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
import { RenderInlineModel } from './Instance';
 | 
					import { InstanceRenderInterface, RenderInlineModel } from './Instance';
 | 
				
			||||||
import { StatusRenderer } from './StatusRenderer';
 | 
					import { StatusRenderer } from './StatusRenderer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -10,9 +10,7 @@ import { StatusRenderer } from './StatusRenderer';
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderPurchaseOrder({
 | 
					export function RenderPurchaseOrder({
 | 
				
			||||||
  instance
 | 
					  instance
 | 
				
			||||||
}: {
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  instance: any;
 | 
					 | 
				
			||||||
}): ReactNode {
 | 
					 | 
				
			||||||
  let supplier = instance.supplier_detail || {};
 | 
					  let supplier = instance.supplier_detail || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO: Handle URL
 | 
					  // TODO: Handle URL
 | 
				
			||||||
@@ -32,7 +30,9 @@ export function RenderPurchaseOrder({
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inline rendering of a single ReturnOrder instance
 | 
					 * Inline rendering of a single ReturnOrder instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderReturnOrder({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderReturnOrder({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  let customer = instance.customer_detail || {};
 | 
					  let customer = instance.customer_detail || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@@ -51,7 +51,9 @@ export function RenderReturnOrder({ instance }: { instance: any }): ReactNode {
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inline rendering of a single SalesOrder instance
 | 
					 * Inline rendering of a single SalesOrder instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderSalesOrder({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderSalesOrder({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  let customer = instance.customer_detail || {};
 | 
					  let customer = instance.customer_detail || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO: Handle URL
 | 
					  // TODO: Handle URL
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,14 @@
 | 
				
			|||||||
import { t } from '@lingui/macro';
 | 
					import { t } from '@lingui/macro';
 | 
				
			||||||
import { ReactNode } from 'react';
 | 
					import { ReactNode } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { RenderInlineModel } from './Instance';
 | 
					import { InstanceRenderInterface, RenderInlineModel } from './Instance';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inline rendering of a single Part instance
 | 
					 * Inline rendering of a single Part instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderPart({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderPart({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  const stock = t`Stock` + `: ${instance.in_stock}`;
 | 
					  const stock = t`Stock` + `: ${instance.in_stock}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@@ -22,7 +24,9 @@ export function RenderPart({ instance }: { instance: any }): ReactNode {
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inline rendering of a PartCategory instance
 | 
					 * Inline rendering of a PartCategory instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderPartCategory({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderPartCategory({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  // TODO: Handle URL
 | 
					  // TODO: Handle URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let lvl = '-'.repeat(instance.level || 0);
 | 
					  let lvl = '-'.repeat(instance.level || 0);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ export interface StatusCodeListInterface {
 | 
				
			|||||||
  [key: string]: StatusCodeInterface;
 | 
					  [key: string]: StatusCodeInterface;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface renderStatusLabelOptionsInterface {
 | 
					interface RenderStatusLabelOptionsInterface {
 | 
				
			||||||
  size?: MantineSize;
 | 
					  size?: MantineSize;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,7 +24,7 @@ interface renderStatusLabelOptionsInterface {
 | 
				
			|||||||
function renderStatusLabel(
 | 
					function renderStatusLabel(
 | 
				
			||||||
  key: string | number,
 | 
					  key: string | number,
 | 
				
			||||||
  codes: StatusCodeListInterface,
 | 
					  codes: StatusCodeListInterface,
 | 
				
			||||||
  options: renderStatusLabelOptionsInterface = {}
 | 
					  options: RenderStatusLabelOptionsInterface = {}
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  let text = null;
 | 
					  let text = null;
 | 
				
			||||||
  let color = null;
 | 
					  let color = null;
 | 
				
			||||||
@@ -70,7 +70,7 @@ export const StatusRenderer = ({
 | 
				
			|||||||
}: {
 | 
					}: {
 | 
				
			||||||
  status: string | number;
 | 
					  status: string | number;
 | 
				
			||||||
  type: ModelType | string;
 | 
					  type: ModelType | string;
 | 
				
			||||||
  options?: renderStatusLabelOptionsInterface;
 | 
					  options?: RenderStatusLabelOptionsInterface;
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const statusCodeList = useGlobalStatusState.getState().status;
 | 
					  const statusCodeList = useGlobalStatusState.getState().status;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,14 @@
 | 
				
			|||||||
import { t } from '@lingui/macro';
 | 
					import { t } from '@lingui/macro';
 | 
				
			||||||
import { ReactNode } from 'react';
 | 
					import { ReactNode } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { RenderInlineModel } from './Instance';
 | 
					import { InstanceRenderInterface, RenderInlineModel } from './Instance';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inline rendering of a single StockLocation instance
 | 
					 * Inline rendering of a single StockLocation instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function RenderStockLocation({
 | 
					export function RenderStockLocation({
 | 
				
			||||||
  instance
 | 
					  instance
 | 
				
			||||||
}: {
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  instance: any;
 | 
					 | 
				
			||||||
}): ReactNode {
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <RenderInlineModel
 | 
					    <RenderInlineModel
 | 
				
			||||||
      primary={instance.name}
 | 
					      primary={instance.name}
 | 
				
			||||||
@@ -19,7 +17,9 @@ export function RenderStockLocation({
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RenderStockItem({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderStockItem({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  let quantity_string = '';
 | 
					  let quantity_string = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (instance?.serial !== null && instance?.serial !== undefined) {
 | 
					  if (instance?.serial !== null && instance?.serial !== undefined) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,11 @@
 | 
				
			|||||||
import { IconUser, IconUsersGroup } from '@tabler/icons-react';
 | 
					import { IconUser, IconUsersGroup } from '@tabler/icons-react';
 | 
				
			||||||
import { ReactNode } from 'react';
 | 
					import { ReactNode } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { RenderInlineModel } from './Instance';
 | 
					import { InstanceRenderInterface, RenderInlineModel } from './Instance';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RenderOwner({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderOwner({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    instance && (
 | 
					    instance && (
 | 
				
			||||||
      <RenderInlineModel
 | 
					      <RenderInlineModel
 | 
				
			||||||
@@ -14,7 +16,9 @@ export function RenderOwner({ instance }: { instance: any }): ReactNode {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RenderUser({ instance }: { instance: any }): ReactNode {
 | 
					export function RenderUser({
 | 
				
			||||||
 | 
					  instance
 | 
				
			||||||
 | 
					}: Readonly<InstanceRenderInterface>): ReactNode {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    instance && (
 | 
					    instance && (
 | 
				
			||||||
      <RenderInlineModel
 | 
					      <RenderInlineModel
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,13 @@ import {
 | 
				
			|||||||
  useUserSettingsState
 | 
					  useUserSettingsState
 | 
				
			||||||
} from '../states/SettingsState';
 | 
					} from '../states/SettingsState';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface formatDecmimalOptionsType {
 | 
					interface FormatDecmimalOptionsInterface {
 | 
				
			||||||
  digits?: number;
 | 
					  digits?: number;
 | 
				
			||||||
  minDigits?: number;
 | 
					  minDigits?: number;
 | 
				
			||||||
  locale?: string;
 | 
					  locale?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface formatCurrencyOptionsType {
 | 
					interface FormatCurrencyOptionsInterface {
 | 
				
			||||||
  digits?: number;
 | 
					  digits?: number;
 | 
				
			||||||
  minDigits?: number;
 | 
					  minDigits?: number;
 | 
				
			||||||
  currency?: string;
 | 
					  currency?: string;
 | 
				
			||||||
@@ -22,7 +22,7 @@ interface formatCurrencyOptionsType {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function formatDecimal(
 | 
					export function formatDecimal(
 | 
				
			||||||
  value: number | null | undefined,
 | 
					  value: number | null | undefined,
 | 
				
			||||||
  options: formatDecmimalOptionsType = {}
 | 
					  options: FormatDecmimalOptionsInterface = {}
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  let locale = options.locale || navigator.language || 'en-US';
 | 
					  let locale = options.locale || navigator.language || 'en-US';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,7 +45,7 @@ export function formatDecimal(
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export function formatCurrency(
 | 
					export function formatCurrency(
 | 
				
			||||||
  value: number | string | null | undefined,
 | 
					  value: number | string | null | undefined,
 | 
				
			||||||
  options: formatCurrencyOptionsType = {}
 | 
					  options: FormatCurrencyOptionsInterface = {}
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  if (value == null || value == undefined) {
 | 
					  if (value == null || value == undefined) {
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
@@ -90,7 +90,7 @@ export function formatCurrency(
 | 
				
			|||||||
export function formatPriceRange(
 | 
					export function formatPriceRange(
 | 
				
			||||||
  minValue: number | null,
 | 
					  minValue: number | null,
 | 
				
			||||||
  maxValue: number | null,
 | 
					  maxValue: number | null,
 | 
				
			||||||
  options: formatCurrencyOptionsType = {}
 | 
					  options: FormatCurrencyOptionsInterface = {}
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  // If neither values are provided, return a dash
 | 
					  // If neither values are provided, return a dash
 | 
				
			||||||
  if (minValue == null && maxValue == null) {
 | 
					  if (minValue == null && maxValue == null) {
 | 
				
			||||||
@@ -117,7 +117,7 @@ export function formatPriceRange(
 | 
				
			|||||||
  )}`;
 | 
					  )}`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface renderDateOptionsType {
 | 
					interface RenderDateOptionsInterface {
 | 
				
			||||||
  showTime?: boolean;
 | 
					  showTime?: boolean;
 | 
				
			||||||
  showSeconds?: boolean;
 | 
					  showSeconds?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -128,7 +128,10 @@ interface renderDateOptionsType {
 | 
				
			|||||||
 * The provided "date" variable is a string, nominally ISO format e.g. 2022-02-22
 | 
					 * The provided "date" variable is a string, nominally ISO format e.g. 2022-02-22
 | 
				
			||||||
 * The user-configured setting DATE_DISPLAY_FORMAT determines how the date should be displayed.
 | 
					 * The user-configured setting DATE_DISPLAY_FORMAT determines how the date should be displayed.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function renderDate(date: string, options: renderDateOptionsType = {}) {
 | 
					export function renderDate(
 | 
				
			||||||
 | 
					  date: string,
 | 
				
			||||||
 | 
					  options: RenderDateOptionsInterface = {}
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
  if (!date) {
 | 
					  if (!date) {
 | 
				
			||||||
    return '-';
 | 
					    return '-';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -217,7 +217,7 @@ type InvenTreeIconProps = {
 | 
				
			|||||||
  iconProps?: TablerIconProps;
 | 
					  iconProps?: TablerIconProps;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function InvenTreeIcon(props: InvenTreeIconProps) {
 | 
					export function InvenTreeIcon(props: Readonly<InvenTreeIconProps>) {
 | 
				
			||||||
  let Icon: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
 | 
					  let Icon: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (props.icon in icons) {
 | 
					  if (props.icon in icons) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										91
									
								
								src/frontend/src/hooks/UseFilter.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/frontend/src/hooks/UseFilter.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Custom hook for retrieving a list of items from the API,
 | 
				
			||||||
 | 
					 * and turning them into "filters" for use in the frontend table framework.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import { useQuery } from '@tanstack/react-query';
 | 
				
			||||||
 | 
					import { useCallback, useMemo } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { api } from '../App';
 | 
				
			||||||
 | 
					import { ApiEndpoints } from '../enums/ApiEndpoints';
 | 
				
			||||||
 | 
					import { resolveItem } from '../functions/conversion';
 | 
				
			||||||
 | 
					import { apiUrl } from '../states/ApiState';
 | 
				
			||||||
 | 
					import { TableFilterChoice } from '../tables/Filter';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UseFilterProps = {
 | 
				
			||||||
 | 
					  url: string;
 | 
				
			||||||
 | 
					  method?: 'GET' | 'POST' | 'OPTIONS';
 | 
				
			||||||
 | 
					  params?: any;
 | 
				
			||||||
 | 
					  accessor?: string;
 | 
				
			||||||
 | 
					  transform: (item: any) => TableFilterChoice;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useFilters(props: UseFilterProps) {
 | 
				
			||||||
 | 
					  const query = useQuery({
 | 
				
			||||||
 | 
					    enabled: true,
 | 
				
			||||||
 | 
					    queryKey: [props.url, props.method, props.params],
 | 
				
			||||||
 | 
					    queryFn: async () => {
 | 
				
			||||||
 | 
					      return await api
 | 
				
			||||||
 | 
					        .request({
 | 
				
			||||||
 | 
					          url: props.url,
 | 
				
			||||||
 | 
					          method: props.method || 'GET',
 | 
				
			||||||
 | 
					          params: props.params
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .then((response) => {
 | 
				
			||||||
 | 
					          let data = resolveItem(response, props.accessor ?? 'data');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (data == null || data == undefined) {
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return data;
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch((error) => []);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const choices: TableFilterChoice[] = useMemo(() => {
 | 
				
			||||||
 | 
					    return query.data?.map(props.transform) ?? [];
 | 
				
			||||||
 | 
					  }, [props.transform, query.data]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const refresh = useCallback(() => {
 | 
				
			||||||
 | 
					    query.refetch();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    choices,
 | 
				
			||||||
 | 
					    refresh
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Provide list of project code filters
 | 
				
			||||||
 | 
					export function useProjectCodeFilters() {
 | 
				
			||||||
 | 
					  return useFilters({
 | 
				
			||||||
 | 
					    url: apiUrl(ApiEndpoints.project_code_list),
 | 
				
			||||||
 | 
					    transform: (item) => ({
 | 
				
			||||||
 | 
					      value: item.pk,
 | 
				
			||||||
 | 
					      label: item.code
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Provide list of user filters
 | 
				
			||||||
 | 
					export function useUserFilters() {
 | 
				
			||||||
 | 
					  return useFilters({
 | 
				
			||||||
 | 
					    url: apiUrl(ApiEndpoints.user_list),
 | 
				
			||||||
 | 
					    transform: (item) => ({
 | 
				
			||||||
 | 
					      value: item.pk,
 | 
				
			||||||
 | 
					      label: item.username
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Provide list of owner filters
 | 
				
			||||||
 | 
					export function useOwnerFilters() {
 | 
				
			||||||
 | 
					  return useFilters({
 | 
				
			||||||
 | 
					    url: apiUrl(ApiEndpoints.owner_list),
 | 
				
			||||||
 | 
					    transform: (item) => ({
 | 
				
			||||||
 | 
					      value: item.pk,
 | 
				
			||||||
 | 
					      label: item.name
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,6 +4,7 @@ import {
 | 
				
			|||||||
  Badge,
 | 
					  Badge,
 | 
				
			||||||
  Button,
 | 
					  Button,
 | 
				
			||||||
  Checkbox,
 | 
					  Checkbox,
 | 
				
			||||||
 | 
					  Col,
 | 
				
			||||||
  Container,
 | 
					  Container,
 | 
				
			||||||
  Grid,
 | 
					  Grid,
 | 
				
			||||||
  Group,
 | 
					  Group,
 | 
				
			||||||
@@ -479,11 +480,11 @@ enum InputMethod {
 | 
				
			|||||||
  ImageBarcode = 'imageBarcode'
 | 
					  ImageBarcode = 'imageBarcode'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface inputProps {
 | 
					interface ScanInputInterface {
 | 
				
			||||||
  action: (items: ScanItem[]) => void;
 | 
					  action: (items: ScanItem[]) => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function InputManual({ action }: inputProps) {
 | 
					function InputManual({ action }: Readonly<ScanInputInterface>) {
 | 
				
			||||||
  const [value, setValue] = useState<string>('');
 | 
					  const [value, setValue] = useState<string>('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function btnAddItem() {
 | 
					  function btnAddItem() {
 | 
				
			||||||
@@ -535,7 +536,7 @@ function InputManual({ action }: inputProps) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Input that uses QR code detection from images */
 | 
					/* Input that uses QR code detection from images */
 | 
				
			||||||
function InputImageBarcode({ action }: inputProps) {
 | 
					function InputImageBarcode({ action }: Readonly<ScanInputInterface>) {
 | 
				
			||||||
  const [qrCodeScanner, setQrCodeScanner] = useState<Html5Qrcode | null>(null);
 | 
					  const [qrCodeScanner, setQrCodeScanner] = useState<Html5Qrcode | null>(null);
 | 
				
			||||||
  const [camId, setCamId] = useLocalStorage<CameraDevice | null>({
 | 
					  const [camId, setCamId] = useLocalStorage<CameraDevice | null>({
 | 
				
			||||||
    key: 'camId',
 | 
					    key: 'camId',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,7 +56,7 @@ export type CompanyDetailProps = {
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Detail view for a single company instance
 | 
					 * Detail view for a single company instance
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export default function CompanyDetail(props: CompanyDetailProps) {
 | 
					export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
 | 
				
			||||||
  const { id } = useParams();
 | 
					  const { id } = useParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const user = useUserState();
 | 
					  const user = useUserState();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,7 +104,7 @@ export function RowActions({
 | 
				
			|||||||
  }, [actions]);
 | 
					  }, [actions]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Render a single action icon
 | 
					  // Render a single action icon
 | 
				
			||||||
  function RowActionIcon(action: RowAction) {
 | 
					  function RowActionIcon(action: Readonly<RowAction>) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <Tooltip
 | 
					      <Tooltip
 | 
				
			||||||
        withinPortal={true}
 | 
					        withinPortal={true}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
				
			|||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
import { UserRoles } from '../../enums/Roles';
 | 
					import { UserRoles } from '../../enums/Roles';
 | 
				
			||||||
import { useBuildOrderFields } from '../../forms/BuildForms';
 | 
					import { useBuildOrderFields } from '../../forms/BuildForms';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  useOwnerFilters,
 | 
				
			||||||
 | 
					  useProjectCodeFilters,
 | 
				
			||||||
 | 
					  useUserFilters
 | 
				
			||||||
 | 
					} from '../../hooks/UseFilter';
 | 
				
			||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
 | 
					import { useCreateApiFormModal } from '../../hooks/UseForm';
 | 
				
			||||||
import { useTable } from '../../hooks/UseTable';
 | 
					import { useTable } from '../../hooks/UseTable';
 | 
				
			||||||
import { apiUrl } from '../../states/ApiState';
 | 
					import { apiUrl } from '../../states/ApiState';
 | 
				
			||||||
@@ -92,6 +97,10 @@ export function BuildOrderTable({
 | 
				
			|||||||
}) {
 | 
					}) {
 | 
				
			||||||
  const tableColumns = useMemo(() => buildOrderTableColumns(), []);
 | 
					  const tableColumns = useMemo(() => buildOrderTableColumns(), []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const projectCodeFilters = useProjectCodeFilters();
 | 
				
			||||||
 | 
					  const userFilters = useUserFilters();
 | 
				
			||||||
 | 
					  const responsibleFilters = useOwnerFilters();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tableFilters: TableFilter[] = useMemo(() => {
 | 
					  const tableFilters: TableFilter[] = useMemo(() => {
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@@ -117,18 +126,36 @@ export function BuildOrderTable({
 | 
				
			|||||||
        type: 'boolean',
 | 
					        type: 'boolean',
 | 
				
			||||||
        label: t`Assigned to me`,
 | 
					        label: t`Assigned to me`,
 | 
				
			||||||
        description: t`Show orders assigned to me`
 | 
					        description: t`Show orders assigned to me`
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'project_code',
 | 
				
			||||||
 | 
					        label: t`Project Code`,
 | 
				
			||||||
 | 
					        description: t`Filter by project code`,
 | 
				
			||||||
 | 
					        choices: projectCodeFilters.choices
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'has_project_code',
 | 
				
			||||||
 | 
					        label: t`Has Project Code`,
 | 
				
			||||||
 | 
					        description: t`Filter by whether the purchase order has a project code`
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'issued_by',
 | 
				
			||||||
 | 
					        label: t`Issued By`,
 | 
				
			||||||
 | 
					        description: t`Filter by user who issued this order`,
 | 
				
			||||||
 | 
					        choices: userFilters.choices
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'assigned_to',
 | 
				
			||||||
 | 
					        label: t`Responsible`,
 | 
				
			||||||
 | 
					        description: t`Filter by responsible owner`,
 | 
				
			||||||
 | 
					        choices: responsibleFilters.choices
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // TODO: 'assigned to' filter
 | 
					 | 
				
			||||||
      // TODO: 'issued by' filter
 | 
					 | 
				
			||||||
      // {
 | 
					 | 
				
			||||||
      //   name: 'has_project_code',
 | 
					 | 
				
			||||||
      //   title: t`Has Project Code`,
 | 
					 | 
				
			||||||
      //   description: t`Show orders with project code`,
 | 
					 | 
				
			||||||
      // }
 | 
					 | 
				
			||||||
      // TODO: 'has project code' filter (see table_filters.js)
 | 
					 | 
				
			||||||
      // TODO: 'project code' filter (see table_filters.js)
 | 
					 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
  }, []);
 | 
					  }, [
 | 
				
			||||||
 | 
					    projectCodeFilters.choices,
 | 
				
			||||||
 | 
					    userFilters.choices,
 | 
				
			||||||
 | 
					    responsibleFilters.choices
 | 
				
			||||||
 | 
					  ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const user = useUserState();
 | 
					  const user = useUserState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -116,13 +116,13 @@ export default function BuildOutputTable({
 | 
				
			|||||||
      />,
 | 
					      />,
 | 
				
			||||||
      <ActionButton
 | 
					      <ActionButton
 | 
				
			||||||
        tooltip={t`Scrap selected outputs`}
 | 
					        tooltip={t`Scrap selected outputs`}
 | 
				
			||||||
        icon={<InvenTreeIcon icon="cancel" />}
 | 
					        icon={<InvenTreeIcon icon="delete" />}
 | 
				
			||||||
        color="red"
 | 
					        color="red"
 | 
				
			||||||
        disabled={!table.hasSelectedRecords}
 | 
					        disabled={!table.hasSelectedRecords}
 | 
				
			||||||
      />,
 | 
					      />,
 | 
				
			||||||
      <ActionButton
 | 
					      <ActionButton
 | 
				
			||||||
        tooltip={t`Cancel selected outputs`}
 | 
					        tooltip={t`Cancel selected outputs`}
 | 
				
			||||||
        icon={<InvenTreeIcon icon="delete" />}
 | 
					        icon={<InvenTreeIcon icon="cancel" />}
 | 
				
			||||||
        color="red"
 | 
					        color="red"
 | 
				
			||||||
        disabled={!table.hasSelectedRecords}
 | 
					        disabled={!table.hasSelectedRecords}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
@@ -153,14 +153,14 @@ export default function BuildOutputTable({
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
          title: t`Scrap`,
 | 
					          title: t`Scrap`,
 | 
				
			||||||
          tooltip: t`Scrap build output`,
 | 
					          tooltip: t`Scrap build output`,
 | 
				
			||||||
          color: 'red',
 | 
					          icon: <InvenTreeIcon icon="delete" />,
 | 
				
			||||||
          icon: <InvenTreeIcon icon="cancel" />
 | 
					          color: 'red'
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          title: t`Delete`,
 | 
					          title: t`Cancel`,
 | 
				
			||||||
          tooltip: t`Delete build output`,
 | 
					          tooltip: t`Cancel build output`,
 | 
				
			||||||
          color: 'red',
 | 
					          icon: <InvenTreeIcon icon="cancel" />,
 | 
				
			||||||
          icon: <InvenTreeIcon icon="delete" />
 | 
					          color: 'red'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ];
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
import { t } from '@lingui/macro';
 | 
					import { t } from '@lingui/macro';
 | 
				
			||||||
import { Group, Text } from '@mantine/core';
 | 
					import { Group, Text } from '@mantine/core';
 | 
				
			||||||
import { ReactNode, useMemo } from 'react';
 | 
					import { ReactNode, useMemo } from 'react';
 | 
				
			||||||
import { useNavigate } from 'react-router-dom';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
 | 
					import { AddItemButton } from '../../components/buttons/AddItemButton';
 | 
				
			||||||
import { formatPriceRange } from '../../defaults/formatters';
 | 
					import { formatPriceRange } from '../../defaults/formatters';
 | 
				
			||||||
@@ -9,7 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
				
			|||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
import { UserRoles } from '../../enums/Roles';
 | 
					import { UserRoles } from '../../enums/Roles';
 | 
				
			||||||
import { usePartFields } from '../../forms/PartForms';
 | 
					import { usePartFields } from '../../forms/PartForms';
 | 
				
			||||||
import { shortenString } from '../../functions/tables';
 | 
					 | 
				
			||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
 | 
					import { useCreateApiFormModal } from '../../hooks/UseForm';
 | 
				
			||||||
import { useTable } from '../../hooks/UseTable';
 | 
					import { useTable } from '../../hooks/UseTable';
 | 
				
			||||||
import { apiUrl } from '../../states/ApiState';
 | 
					import { apiUrl } from '../../states/ApiState';
 | 
				
			||||||
@@ -43,13 +41,7 @@ function partTableColumns(): TableColumn[] {
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
      accessor: 'category',
 | 
					      accessor: 'category',
 | 
				
			||||||
      sortable: true,
 | 
					      sortable: true,
 | 
				
			||||||
 | 
					      render: (record: any) => record.category_detail?.pathstring
 | 
				
			||||||
      render: function (record: any) {
 | 
					 | 
				
			||||||
        // TODO: Link to the category detail page
 | 
					 | 
				
			||||||
        return shortenString({
 | 
					 | 
				
			||||||
          str: record.category_detail?.pathstring
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      accessor: 'total_in_stock',
 | 
					      accessor: 'total_in_stock',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,7 +52,11 @@ type ThumbProps = {
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Renders a single image thumbnail
 | 
					 * Renders a single image thumbnail
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function PartThumbComponent({ selected, element, selectImage }: ThumbProps) {
 | 
					function PartThumbComponent({
 | 
				
			||||||
 | 
					  selected,
 | 
				
			||||||
 | 
					  element,
 | 
				
			||||||
 | 
					  selectImage
 | 
				
			||||||
 | 
					}: Readonly<ThumbProps>) {
 | 
				
			||||||
  const { hovered, ref } = useHover();
 | 
					  const { hovered, ref } = useHover();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const hoverColor = 'rgba(127,127,127,0.2)';
 | 
					  const hoverColor = 'rgba(127,127,127,0.2)';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
				
			|||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
import { UserRoles } from '../../enums/Roles';
 | 
					import { UserRoles } from '../../enums/Roles';
 | 
				
			||||||
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
 | 
					import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
 | 
				
			||||||
 | 
					import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
 | 
				
			||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
 | 
					import { useCreateApiFormModal } from '../../hooks/UseForm';
 | 
				
			||||||
import { useTable } from '../../hooks/UseTable';
 | 
					import { useTable } from '../../hooks/UseTable';
 | 
				
			||||||
import { apiUrl } from '../../states/ApiState';
 | 
					import { apiUrl } from '../../states/ApiState';
 | 
				
			||||||
@@ -44,6 +45,9 @@ export function PurchaseOrderTable({
 | 
				
			|||||||
  const table = useTable('purchase-order');
 | 
					  const table = useTable('purchase-order');
 | 
				
			||||||
  const user = useUserState();
 | 
					  const user = useUserState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const projectCodeFilters = useProjectCodeFilters();
 | 
				
			||||||
 | 
					  const responsibleFilters = useOwnerFilters();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tableFilters: TableFilter[] = useMemo(() => {
 | 
					  const tableFilters: TableFilter[] = useMemo(() => {
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@@ -54,11 +58,26 @@ export function PurchaseOrderTable({
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      OutstandingFilter(),
 | 
					      OutstandingFilter(),
 | 
				
			||||||
      OverdueFilter(),
 | 
					      OverdueFilter(),
 | 
				
			||||||
      AssignedToMeFilter()
 | 
					      AssignedToMeFilter(),
 | 
				
			||||||
      // TODO: has_project_code
 | 
					      {
 | 
				
			||||||
      // TODO: project_code
 | 
					        name: 'project_code',
 | 
				
			||||||
 | 
					        label: t`Project Code`,
 | 
				
			||||||
 | 
					        description: t`Filter by project code`,
 | 
				
			||||||
 | 
					        choices: projectCodeFilters.choices
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'has_project_code',
 | 
				
			||||||
 | 
					        label: t`Has Project Code`,
 | 
				
			||||||
 | 
					        description: t`Filter by whether the purchase order has a project code`
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'assigned_to',
 | 
				
			||||||
 | 
					        label: t`Responsible`,
 | 
				
			||||||
 | 
					        description: t`Filter by responsible owner`,
 | 
				
			||||||
 | 
					        choices: responsibleFilters.choices
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
  }, []);
 | 
					  }, [projectCodeFilters.choices, responsibleFilters.choices]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tableColumns = useMemo(() => {
 | 
					  const tableColumns = useMemo(() => {
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
				
			|||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
import { UserRoles } from '../../enums/Roles';
 | 
					import { UserRoles } from '../../enums/Roles';
 | 
				
			||||||
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
 | 
					import { useReturnOrderFields } from '../../forms/SalesOrderForms';
 | 
				
			||||||
 | 
					import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
 | 
				
			||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
 | 
					import { useCreateApiFormModal } from '../../hooks/UseForm';
 | 
				
			||||||
import { useTable } from '../../hooks/UseTable';
 | 
					import { useTable } from '../../hooks/UseTable';
 | 
				
			||||||
import { apiUrl } from '../../states/ApiState';
 | 
					import { apiUrl } from '../../states/ApiState';
 | 
				
			||||||
@@ -35,6 +36,9 @@ export function ReturnOrderTable({ params }: { params?: any }) {
 | 
				
			|||||||
  const table = useTable('return-orders');
 | 
					  const table = useTable('return-orders');
 | 
				
			||||||
  const user = useUserState();
 | 
					  const user = useUserState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const projectCodeFilters = useProjectCodeFilters();
 | 
				
			||||||
 | 
					  const responsibleFilters = useOwnerFilters();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tableFilters: TableFilter[] = useMemo(() => {
 | 
					  const tableFilters: TableFilter[] = useMemo(() => {
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@@ -45,9 +49,26 @@ export function ReturnOrderTable({ params }: { params?: any }) {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      OutstandingFilter(),
 | 
					      OutstandingFilter(),
 | 
				
			||||||
      OverdueFilter(),
 | 
					      OverdueFilter(),
 | 
				
			||||||
      AssignedToMeFilter()
 | 
					      AssignedToMeFilter(),
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'project_code',
 | 
				
			||||||
 | 
					        label: t`Project Code`,
 | 
				
			||||||
 | 
					        description: t`Filter by project code`,
 | 
				
			||||||
 | 
					        choices: projectCodeFilters.choices
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'has_project_code',
 | 
				
			||||||
 | 
					        label: t`Has Project Code`,
 | 
				
			||||||
 | 
					        description: t`Filter by whether the purchase order has a project code`
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'assigned_to',
 | 
				
			||||||
 | 
					        label: t`Responsible`,
 | 
				
			||||||
 | 
					        description: t`Filter by responsible owner`,
 | 
				
			||||||
 | 
					        choices: responsibleFilters.choices
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
  }, []);
 | 
					  }, [projectCodeFilters.choices, responsibleFilters.choices]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tableColumns = useMemo(() => {
 | 
					  const tableColumns = useMemo(() => {
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
				
			|||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
import { UserRoles } from '../../enums/Roles';
 | 
					import { UserRoles } from '../../enums/Roles';
 | 
				
			||||||
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
 | 
					import { useSalesOrderFields } from '../../forms/SalesOrderForms';
 | 
				
			||||||
 | 
					import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
 | 
				
			||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
 | 
					import { useCreateApiFormModal } from '../../hooks/UseForm';
 | 
				
			||||||
import { useTable } from '../../hooks/UseTable';
 | 
					import { useTable } from '../../hooks/UseTable';
 | 
				
			||||||
import { apiUrl } from '../../states/ApiState';
 | 
					import { apiUrl } from '../../states/ApiState';
 | 
				
			||||||
@@ -41,6 +42,9 @@ export function SalesOrderTable({
 | 
				
			|||||||
  const table = useTable('sales-order');
 | 
					  const table = useTable('sales-order');
 | 
				
			||||||
  const user = useUserState();
 | 
					  const user = useUserState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const projectCodeFilters = useProjectCodeFilters();
 | 
				
			||||||
 | 
					  const responsibleFilters = useOwnerFilters();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tableFilters: TableFilter[] = useMemo(() => {
 | 
					  const tableFilters: TableFilter[] = useMemo(() => {
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
@@ -51,11 +55,26 @@ export function SalesOrderTable({
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      OutstandingFilter(),
 | 
					      OutstandingFilter(),
 | 
				
			||||||
      OverdueFilter(),
 | 
					      OverdueFilter(),
 | 
				
			||||||
      AssignedToMeFilter()
 | 
					      AssignedToMeFilter(),
 | 
				
			||||||
      // TODO: has_project_code
 | 
					      {
 | 
				
			||||||
      // TODO: project_code
 | 
					        name: 'project_code',
 | 
				
			||||||
 | 
					        label: t`Project Code`,
 | 
				
			||||||
 | 
					        description: t`Filter by project code`,
 | 
				
			||||||
 | 
					        choices: projectCodeFilters.choices
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'has_project_code',
 | 
				
			||||||
 | 
					        label: t`Has Project Code`,
 | 
				
			||||||
 | 
					        description: t`Filter by whether the purchase order has a project code`
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        name: 'assigned_to',
 | 
				
			||||||
 | 
					        label: t`Responsible`,
 | 
				
			||||||
 | 
					        description: t`Filter by responsible owner`,
 | 
				
			||||||
 | 
					        choices: responsibleFilters.choices
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
  }, []);
 | 
					  }, [projectCodeFilters.choices, responsibleFilters.choices]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const salesOrderFields = useSalesOrderFields();
 | 
					  const salesOrderFields = useSalesOrderFields();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ import { ReactNode, useMemo } from 'react';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
 | 
					import { AddItemButton } from '../../components/buttons/AddItemButton';
 | 
				
			||||||
import { ActionDropdown } from '../../components/items/ActionDropdown';
 | 
					import { ActionDropdown } from '../../components/items/ActionDropdown';
 | 
				
			||||||
import { formatCurrency, renderDate } from '../../defaults/formatters';
 | 
					import { formatCurrency } from '../../defaults/formatters';
 | 
				
			||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
					import { ApiEndpoints } from '../../enums/ApiEndpoints';
 | 
				
			||||||
import { ModelType } from '../../enums/ModelType';
 | 
					import { ModelType } from '../../enums/ModelType';
 | 
				
			||||||
import { UserRoles } from '../../enums/Roles';
 | 
					import { UserRoles } from '../../enums/Roles';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,10 +27,19 @@ test('PUI - Parts', async ({ page }) => {
 | 
				
			|||||||
  await page.getByText('1551ACLR').click();
 | 
					  await page.getByText('1551ACLR').click();
 | 
				
			||||||
  await page.getByRole('tab', { name: 'Part Details' }).click();
 | 
					  await page.getByRole('tab', { name: 'Part Details' }).click();
 | 
				
			||||||
  await page.getByRole('tab', { name: 'Parameters' }).click();
 | 
					  await page.getByRole('tab', { name: 'Parameters' }).click();
 | 
				
			||||||
  // await page.getByRole('tab', { name: 'Stock' }).click();
 | 
					  await page
 | 
				
			||||||
 | 
					    .getByRole('tab', { name: 'Part Details' })
 | 
				
			||||||
 | 
					    .locator('xpath=..')
 | 
				
			||||||
 | 
					    .getByRole('tab', { name: 'Stock', exact: true })
 | 
				
			||||||
 | 
					    .click();
 | 
				
			||||||
  await page.getByRole('tab', { name: 'Allocations' }).click();
 | 
					  await page.getByRole('tab', { name: 'Allocations' }).click();
 | 
				
			||||||
  await page.getByRole('tab', { name: 'Used In' }).click();
 | 
					  await page.getByRole('tab', { name: 'Used In' }).click();
 | 
				
			||||||
  await page.getByRole('tab', { name: 'Pricing' }).click();
 | 
					  await page.getByRole('tab', { name: 'Pricing' }).click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await page.goto(`${baseUrl}/part/category/index/parts`);
 | 
				
			||||||
 | 
					  await page.getByText('Blue Chair').click();
 | 
				
			||||||
 | 
					  await page.getByRole('tab', { name: 'Bill of Materials' }).click();
 | 
				
			||||||
 | 
					  await page.getByRole('tab', { name: 'Build Orders' }).click();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test('PUI - Parts - Manufacturer Parts', async ({ page }) => {
 | 
					test('PUI - Parts - Manufacturer Parts', async ({ page }) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,14 @@ test('PUI - Stock', async ({ page }) => {
 | 
				
			|||||||
  await page.getByRole('tab', { name: 'Stock Locations' }).click();
 | 
					  await page.getByRole('tab', { name: 'Stock Locations' }).click();
 | 
				
			||||||
  await page.getByRole('tab', { name: 'Stock Items' }).click();
 | 
					  await page.getByRole('tab', { name: 'Stock Items' }).click();
 | 
				
			||||||
  await page.getByRole('tab', { name: 'Location Details' }).click();
 | 
					  await page.getByRole('tab', { name: 'Location Details' }).click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await page.goto(`${baseUrl}/stock/item/1194/details`);
 | 
				
			||||||
 | 
					  await page.getByText('D.123 | Doohickey').waitFor();
 | 
				
			||||||
 | 
					  await page.getByText('Batch Code: BX-123-2024-2-7').waitFor();
 | 
				
			||||||
 | 
					  await page.getByRole('tab', { name: 'Stock Tracking' }).click();
 | 
				
			||||||
 | 
					  await page.getByRole('tab', { name: 'Test Data' }).click();
 | 
				
			||||||
 | 
					  await page.getByText('395c6d5586e5fb656901d047be27e1f7').waitFor();
 | 
				
			||||||
 | 
					  await page.getByRole('tab', { name: 'Installed Items' }).click();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test('PUI - Build', async ({ page }) => {
 | 
					test('PUI - Build', async ({ page }) => {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user