From 475e64ff4842f35c7933ee1fc68c725233cce9f9 Mon Sep 17 00:00:00 2001 From: Donkie Date: Sun, 30 Jul 2023 09:54:33 +0200 Subject: [PATCH] Client: Added a spool selection dialog --- .../printing/qrCodePrintingDialog.tsx | 4 +- .../src/components/selectAndPrintDialog.tsx | 51 +++++ client/src/components/spoolSelectModal.tsx | 206 ++++++++++++++++++ client/src/pages/spools/list.tsx | 2 + 4 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 client/src/components/selectAndPrintDialog.tsx create mode 100644 client/src/components/spoolSelectModal.tsx diff --git a/client/src/components/printing/qrCodePrintingDialog.tsx b/client/src/components/printing/qrCodePrintingDialog.tsx index 0b72416..fbc886e 100644 --- a/client/src/components/printing/qrCodePrintingDialog.tsx +++ b/client/src/components/printing/qrCodePrintingDialog.tsx @@ -11,11 +11,13 @@ interface QRCodeData { interface QRCodePrintingDialogProps { visible: boolean; items: QRCodeData[]; + onCancel: () => void; } const QRCodePrintingDialog: React.FC = ({ visible, items, + onCancel, }) => { const [showContent, setShowContent] = useSavedState( "print-showContent", @@ -100,7 +102,7 @@ const QRCodePrintingDialog: React.FC = ({ width: auto; } `} - onCancel={() => true} + onCancel={onCancel} /> ); }; diff --git a/client/src/components/selectAndPrintDialog.tsx b/client/src/components/selectAndPrintDialog.tsx new file mode 100644 index 0000000..6a812e0 --- /dev/null +++ b/client/src/components/selectAndPrintDialog.tsx @@ -0,0 +1,51 @@ +// React FC that combines the functionality of first selecting what spools to print QR code for, and then opens up the QR code printing dialog. + +import React from "react"; +import SpoolSelectModal from "./spoolSelectModal"; +import QRCodePrintingDialog from "./printing/qrCodePrintingDialog"; +import { Button } from "antd"; +import { ISpool } from "../pages/spools/model"; +import { PrinterOutlined } from "@ant-design/icons"; + +const SelectAndPrint: React.FC = () => { + const [step, setStep] = React.useState(0); + const [selectedSpools, setSelectedSpools] = React.useState([]); + return ( + <> + + { + setStep(0); + }} + onContinue={(spools) => { + setSelectedSpools(spools); + setStep(2); + }} + /> + { + setStep(1); + }} + items={selectedSpools.map((spool) => ({ + value: `S:${spool.id}`, + // value: `https://spoolman.lan/spool/show/${spool.id}`, + // label: `hello`, + errorLevel: "H", + }))} + /> + + ); +}; + +export default SelectAndPrint; diff --git a/client/src/components/spoolSelectModal.tsx b/client/src/components/spoolSelectModal.tsx new file mode 100644 index 0000000..8d158a5 --- /dev/null +++ b/client/src/components/spoolSelectModal.tsx @@ -0,0 +1,206 @@ +import React, { useState } from "react"; +import { Modal, Table, Checkbox, Space, Row, Col } from "antd"; +import { ISpool } from "../pages/spools/model"; +import { FilteredColumn, SortedColumn, SpoolIconColumn } from "./column"; +import { TableState } from "../utils/saveload"; +import { useTable } from "@refinedev/antd"; +import { genericSorter, typeSorters } from "../utils/sorting"; +import { genericFilterer, typeFilters } from "../utils/filtering"; + +interface Props { + visible: boolean; + description?: string; + onCancel: () => void; + onContinue: (selectedSpools: ISpool[]) => void; +} + +interface ISpoolCollapsed extends ISpool { + filament_name: string; + material?: string; +} + +const SpoolSelectModal: React.FC = ({ + visible, + description, + onCancel, + onContinue, +}) => { + const [selectedItems, setSelectedItems] = useState([]); + const [showArchived, setShowArchived] = useState(false); + + const { tableProps, sorters, filters, current, pageSize } = useTable({ + meta: { + queryParams: { + ["allow_archived"]: showArchived, + }, + }, + syncWithLocation: false, + pagination: { + mode: "off", + current: 1, + pageSize: 10, + }, + sorters: { + mode: "off", + }, + filters: { + mode: "off", + }, + }); + + // Store state in local storage + const tableState: TableState = { + sorters, + filters, + pagination: { current, pageSize }, + }; + + // Collapse the dataSource to a mutable list and add a filament_name field + const dataSource: ISpoolCollapsed[] = React.useMemo( + () => + (tableProps.dataSource ?? []).map((element) => { + let filament_name: string; + if (element.filament.vendor && "name" in element.filament.vendor) { + filament_name = `${element.filament.vendor.name} - ${element.filament.name}`; + } else { + filament_name = + element.filament.name ?? element.filament.id.toString(); + } + return { + ...element, + filament_name, + material: element.filament.material, + }; + }), + [tableProps.dataSource] + ); + + // Type the sorters and filters + const typedSorters = typeSorters(sorters); + const typedFilters = typeFilters(filters); + + // Filter and sort the dataSource + const filteredDataSource = React.useMemo(() => { + const filtered = dataSource.filter(genericFilterer(typedFilters)); + filtered.sort(genericSorter(typedSorters)); + return filtered; + }, [dataSource, typedFilters, typedSorters]); + + // Function to add/remove all filtered items from selected items + const selectUnselectFiltered = (select: boolean) => { + setSelectedItems((prevSelected) => { + const filtered = filteredDataSource + .map((spool) => spool.id) + .filter((spool) => !prevSelected.includes(spool)); + return select ? [...prevSelected, ...filtered] : filtered; + }); + }; + + // Handler for selecting/unselecting individual items + const handleSelectItem = (item: number) => { + setSelectedItems((prevSelected) => + prevSelected.includes(item) + ? prevSelected.filter((selected) => selected !== item) + : [...prevSelected, item] + ); + }; + + // State for the select/unselect all checkbox + const isAllFilteredSelected = filteredDataSource.every((spool) => + selectedItems.includes(spool.id) + ); + + return ( + { + onContinue( + dataSource.filter((spool) => selectedItems.includes(spool.id)) + ); + }} + width={600} + > + + {description &&
{description}
} + + ( + handleSelectItem(item.id)} + /> + )} + /> + {SortedColumn({ + id: "id", + i18ncat: "spool", + dataSource, + tableState, + })} + {SpoolIconColumn({ + id: "filament_name", + i18ncat: "spool", + color: (record: ISpoolCollapsed) => record.filament.color_hex, + dataSource, + tableState, + })} + {FilteredColumn({ + id: "material", + i18ncat: "spool", + dataSource, + tableState, + })} +
+ + + { + selectUnselectFiltered(e.target.checked); + }} + > + Select/Unselect All + + + +
+ {selectedItems.length} spool + {selectedItems.length === 1 ? "" : "s"} selected +
+ + + { + setShowArchived(e.target.checked); + if (!e.target.checked) { + // Remove archived spools from selected items + setSelectedItems((prevSelected) => + prevSelected.filter( + (selected) => + dataSource.find((spool) => spool.id === selected) + ?.archived !== true + ) + ); + } + }} + > + Show Archived + + +
+
+
+ ); +}; + +export default SpoolSelectModal; diff --git a/client/src/pages/spools/list.tsx b/client/src/pages/spools/list.tsx index 0918c30..9cddaa7 100644 --- a/client/src/pages/spools/list.tsx +++ b/client/src/pages/spools/list.tsx @@ -37,6 +37,7 @@ import { SpoolIconColumn, } from "../../components/column"; import { setSpoolArchived } from "./functions"; +import SelectAndPrint from "../../components/selectAndPrintDialog"; dayjs.extend(utc); @@ -191,6 +192,7 @@ export const SpoolList: React.FC = () => { ( <> +