From 0c9e986796042cbb3d2c4349a100aea0f2b255c2 Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@gmail.com>
Date: Wed, 16 Oct 2024 20:18:59 +1100
Subject: [PATCH] Table date filters (#8299)

* Add date filtering for tables

* Update filters for build order table

* Update other tables

* Typing fix

* Fix filter type
---
 src/frontend/src/tables/Filter.tsx            | 45 ++++++++++++++--
 .../src/tables/FilterSelectDrawer.tsx         | 51 +++++++++++++++----
 .../src/tables/build/BuildOrderTable.tsx      | 32 +++++-------
 .../tables/purchasing/PurchaseOrderTable.tsx  | 11 ++--
 .../src/tables/sales/ReturnOrderTable.tsx     | 11 ++--
 .../src/tables/sales/SalesOrderTable.tsx      | 11 ++--
 .../src/tables/settings/TemplateTable.tsx     |  2 +-
 7 files changed, 116 insertions(+), 47 deletions(-)

diff --git a/src/frontend/src/tables/Filter.tsx b/src/frontend/src/tables/Filter.tsx
index 4105286703..9be775352b 100644
--- a/src/frontend/src/tables/Filter.tsx
+++ b/src/frontend/src/tables/Filter.tsx
@@ -11,6 +11,15 @@ export type TableFilterChoice = {
   label: string;
 };
 
+/**
+ * Available filter types
+ *
+ * boolean: A simple true/false filter
+ * choice: A filter which allows selection from a list of (supplied)
+ * date: A filter which allows selection from a date input
+ */
+export type TableFilterType = 'boolean' | 'choice' | 'date';
+
 /**
  * Interface for the table filter type. Provides a number of options for selecting filter value:
  *
@@ -22,7 +31,7 @@ export type TableFilter = {
   name: string;
   label: string;
   description?: string;
-  type?: string;
+  type?: TableFilterType;
   choices?: TableFilterChoice[];
   choiceFunction?: () => TableFilterChoice[];
   defaultValue?: any;
@@ -83,9 +92,12 @@ export function StatusFilterOptions(
   };
 }
 
+// Define some commonly used filters
+
 export function AssignedToMeFilter(): TableFilter {
   return {
     name: 'assigned_to_me',
+    type: 'boolean',
     label: t`Assigned to me`,
     description: t`Show orders assigned to me`
   };
@@ -95,7 +107,7 @@ export function OutstandingFilter(): TableFilter {
   return {
     name: 'outstanding',
     label: t`Outstanding`,
-    description: t`Show outstanding orders`
+    description: t`Show outstanding items`
   };
 }
 
@@ -103,6 +115,33 @@ export function OverdueFilter(): TableFilter {
   return {
     name: 'overdue',
     label: t`Overdue`,
-    description: t`Show overdue orders`
+    description: t`Show overdue items`
+  };
+}
+
+export function MinDateFilter(): TableFilter {
+  return {
+    name: 'min_date',
+    label: t`Minimum Date`,
+    description: t`Show items after this date`,
+    type: 'date'
+  };
+}
+
+export function MaxDateFilter(): TableFilter {
+  return {
+    name: 'max_date',
+    label: t`Maximum Date`,
+    description: t`Show items before this date`,
+    type: 'date'
+  };
+}
+
+export function HasProjectCodeFilter(): TableFilter {
+  return {
+    name: 'has_project_code',
+    type: 'boolean',
+    label: t`Has Project Code`,
+    description: t`Show orders with an assigned project code`
   };
 }
diff --git a/src/frontend/src/tables/FilterSelectDrawer.tsx b/src/frontend/src/tables/FilterSelectDrawer.tsx
index ce214c496b..de9b40202b 100644
--- a/src/frontend/src/tables/FilterSelectDrawer.tsx
+++ b/src/frontend/src/tables/FilterSelectDrawer.tsx
@@ -1,4 +1,5 @@
 import { t } from '@lingui/macro';
+import { L } from '@lingui/react/dist/shared/react.e5f95de8';
 import {
   Badge,
   Button,
@@ -12,6 +13,8 @@ import {
   Text,
   Tooltip
 } from '@mantine/core';
+import { DateInput, DateValue } from '@mantine/dates';
+import dayjs from 'dayjs';
 import { useCallback, useEffect, useMemo, useState } from 'react';
 
 import { StylishText } from '../components/items/StylishText';
@@ -19,6 +22,7 @@ import { TableState } from '../hooks/UseTable';
 import {
   TableFilter,
   TableFilterChoice,
+  TableFilterType,
   getTableFilterOptions
 } from './Filter';
 
@@ -99,6 +103,14 @@ function FilterAddGroup({
     return getTableFilterOptions(filter);
   }, [selectedFilter]);
 
+  // Determine the "type" of filter (default = boolean)
+  const filterType: TableFilterType = useMemo(() => {
+    return (
+      availableFilters?.find((flt) => flt.name === selectedFilter)?.type ??
+      'boolean'
+    );
+  }, [selectedFilter]);
+
   const setSelectedValue = useCallback(
     (value: string | null) => {
       // Find the matching filter
@@ -126,6 +138,18 @@ function FilterAddGroup({
     [selectedFilter]
   );
 
+  const setDateValue = useCallback(
+    (value: DateValue) => {
+      if (value) {
+        let date = value.toString();
+        setSelectedValue(dayjs(date).format('YYYY-MM-DD'));
+      } else {
+        setSelectedValue('');
+      }
+    },
+    [setSelectedValue]
+  );
+
   return (
     <Stack gap="xs">
       <Divider />
@@ -137,16 +161,23 @@ function FilterAddGroup({
         onChange={(value: string | null) => setSelectedFilter(value)}
         maxDropdownHeight={800}
       />
-      {selectedFilter && (
-        <Select
-          data={valueOptions}
-          label={t`Value`}
-          searchable={true}
-          placeholder={t`Select filter value`}
-          onChange={(value: string | null) => setSelectedValue(value)}
-          maxDropdownHeight={800}
-        />
-      )}
+      {selectedFilter &&
+        (filterType === 'date' ? (
+          <DateInput
+            label={t`Value`}
+            placeholder={t`Select date value`}
+            onChange={setDateValue}
+          />
+        ) : (
+          <Select
+            data={valueOptions}
+            label={t`Value`}
+            searchable={true}
+            placeholder={t`Select filter value`}
+            onChange={(value: string | null) => setSelectedValue(value)}
+            maxDropdownHeight={800}
+          />
+        ))}
     </Stack>
   );
 }
diff --git a/src/frontend/src/tables/build/BuildOrderTable.tsx b/src/frontend/src/tables/build/BuildOrderTable.tsx
index 553ad44f3b..d09d476322 100644
--- a/src/frontend/src/tables/build/BuildOrderTable.tsx
+++ b/src/frontend/src/tables/build/BuildOrderTable.tsx
@@ -23,7 +23,15 @@ import {
   StatusColumn,
   TargetDateColumn
 } from '../ColumnRenderers';
-import { StatusFilterOptions, TableFilter } from '../Filter';
+import {
+  AssignedToMeFilter,
+  HasProjectCodeFilter,
+  MaxDateFilter,
+  MinDateFilter,
+  OverdueFilter,
+  StatusFilterOptions,
+  TableFilter
+} from '../Filter';
 import { InvenTreeTable } from '../InvenTreeTable';
 
 /*
@@ -115,29 +123,17 @@ export function BuildOrderTable({
         description: t`Filter by order status`,
         choiceFunction: StatusFilterOptions(ModelType.build)
       },
-      {
-        name: 'overdue',
-        label: t`Overdue`,
-        type: 'boolean',
-        description: t`Show overdue status`
-      },
-      {
-        name: 'assigned_to_me',
-        type: 'boolean',
-        label: t`Assigned to me`,
-        description: t`Show orders assigned to me`
-      },
+      OverdueFilter(),
+      AssignedToMeFilter(),
+      MinDateFilter(),
+      MaxDateFilter(),
       {
         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`
-      },
+      HasProjectCodeFilter(),
       {
         name: 'issued_by',
         label: t`Issued By`,
diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx
index 9eba0509b2..e95166ca75 100644
--- a/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx
+++ b/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx
@@ -25,6 +25,9 @@ import {
 } from '../ColumnRenderers';
 import {
   AssignedToMeFilter,
+  HasProjectCodeFilter,
+  MaxDateFilter,
+  MinDateFilter,
   OutstandingFilter,
   OverdueFilter,
   StatusFilterOptions,
@@ -59,17 +62,15 @@ export function PurchaseOrderTable({
       OutstandingFilter(),
       OverdueFilter(),
       AssignedToMeFilter(),
+      MinDateFilter(),
+      MaxDateFilter(),
       {
         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`
-      },
+      HasProjectCodeFilter(),
       {
         name: 'assigned_to',
         label: t`Responsible`,
diff --git a/src/frontend/src/tables/sales/ReturnOrderTable.tsx b/src/frontend/src/tables/sales/ReturnOrderTable.tsx
index 889933e805..7e6b35e999 100644
--- a/src/frontend/src/tables/sales/ReturnOrderTable.tsx
+++ b/src/frontend/src/tables/sales/ReturnOrderTable.tsx
@@ -25,6 +25,9 @@ import {
 } from '../ColumnRenderers';
 import {
   AssignedToMeFilter,
+  HasProjectCodeFilter,
+  MaxDateFilter,
+  MinDateFilter,
   OutstandingFilter,
   OverdueFilter,
   StatusFilterOptions,
@@ -50,17 +53,15 @@ export function ReturnOrderTable({ params }: Readonly<{ params?: any }>) {
       OutstandingFilter(),
       OverdueFilter(),
       AssignedToMeFilter(),
+      MinDateFilter(),
+      MaxDateFilter(),
       {
         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`
-      },
+      HasProjectCodeFilter(),
       {
         name: 'assigned_to',
         label: t`Responsible`,
diff --git a/src/frontend/src/tables/sales/SalesOrderTable.tsx b/src/frontend/src/tables/sales/SalesOrderTable.tsx
index b96aa3f2cc..96d9da6389 100644
--- a/src/frontend/src/tables/sales/SalesOrderTable.tsx
+++ b/src/frontend/src/tables/sales/SalesOrderTable.tsx
@@ -26,6 +26,9 @@ import {
 } from '../ColumnRenderers';
 import {
   AssignedToMeFilter,
+  HasProjectCodeFilter,
+  MaxDateFilter,
+  MinDateFilter,
   OutstandingFilter,
   OverdueFilter,
   StatusFilterOptions,
@@ -57,17 +60,15 @@ export function SalesOrderTable({
       OutstandingFilter(),
       OverdueFilter(),
       AssignedToMeFilter(),
+      MinDateFilter(),
+      MaxDateFilter(),
       {
         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`
-      },
+      HasProjectCodeFilter(),
       {
         name: 'assigned_to',
         label: t`Responsible`,
diff --git a/src/frontend/src/tables/settings/TemplateTable.tsx b/src/frontend/src/tables/settings/TemplateTable.tsx
index 30db4b931b..ffb915e8cb 100644
--- a/src/frontend/src/tables/settings/TemplateTable.tsx
+++ b/src/frontend/src/tables/settings/TemplateTable.tsx
@@ -355,7 +355,7 @@ export function TemplateTable({
         name: 'enabled',
         label: t`Enabled`,
         description: t`Filter by enabled status`,
-        type: 'checkbox'
+        type: 'boolean'
       },
       {
         name: 'model_type',