/ client / src / pages / printing / spoolSelectModal.tsx
spoolSelectModal.tsx
  1  import { RightOutlined } from "@ant-design/icons";
  2  import { useTable } from "@refinedev/antd";
  3  import { Button, Checkbox, Col, message, Row, Space, Table } from "antd";
  4  import { t } from "i18next";
  5  import { useMemo, useState } from "react";
  6  import { useNavigate } from "react-router";
  7  import { FilteredQueryColumn, SortedColumn, SpoolIconColumn } from "../../components/column";
  8  import { useSpoolmanFilamentFilter, useSpoolmanMaterials } from "../../components/otherModels";
  9  import { removeUndefined } from "../../utils/filtering";
 10  import { TableState } from "../../utils/saveload";
 11  import { ISpool } from "../spools/model";
 12  
 13  interface Props {
 14    description?: string;
 15    onContinue: (selectedSpools: ISpool[]) => void;
 16  }
 17  
 18  interface ISpoolCollapsed extends ISpool {
 19    "filament.combined_name": string;
 20    "filament.id": number;
 21    "filament.material"?: string;
 22  }
 23  
 24  function collapseSpool(element: ISpool): ISpoolCollapsed {
 25    let filament_name: string;
 26    if (element.filament.vendor && "name" in element.filament.vendor) {
 27      filament_name = `${element.filament.vendor.name} - ${element.filament.name}`;
 28    } else {
 29      filament_name = element.filament.name ?? element.filament.id.toString();
 30    }
 31    return {
 32      ...element,
 33      "filament.combined_name": filament_name,
 34      "filament.id": element.filament.id,
 35      "filament.material": element.filament.material,
 36    };
 37  }
 38  
 39  const SpoolSelectModal = ({ description, onContinue }: Props) => {
 40    const [selectedItems, setSelectedItems] = useState<number[]>([]);
 41    const [showArchived, setShowArchived] = useState(false);
 42    const [messageApi, contextHolder] = message.useMessage();
 43    const navigate = useNavigate();
 44  
 45    const { tableProps, sorters, filters, currentPage, pageSize } = useTable<ISpoolCollapsed>({
 46      resource: "spool",
 47      meta: {
 48        queryParams: {
 49          ["allow_archived"]: showArchived,
 50        },
 51      },
 52      syncWithLocation: false,
 53      pagination: {
 54        mode: "off",
 55        currentPage: 1,
 56        pageSize: 10,
 57      },
 58      sorters: {
 59        mode: "server",
 60      },
 61      filters: {
 62        mode: "server",
 63      },
 64      queryOptions: {
 65        select(data) {
 66          return {
 67            total: data.total,
 68            data: data.data.map(collapseSpool),
 69          };
 70        },
 71      },
 72    });
 73  
 74    // Store state in local storage
 75    const tableState: TableState = {
 76      sorters,
 77      filters,
 78      pagination: { currentPage: currentPage, pageSize },
 79    };
 80  
 81    // Collapse the dataSource to a mutable list and add a filament_name field
 82    const dataSource: ISpoolCollapsed[] = useMemo(
 83      () => (tableProps.dataSource || []).map((record) => ({ ...record })),
 84      [tableProps.dataSource],
 85    );
 86  
 87    // Function to add/remove all filtered items from selected items
 88    const selectUnselectFiltered = (select: boolean) => {
 89      setSelectedItems((prevSelected) => {
 90        const filtered = dataSource.map((spool) => spool.id).filter((spool) => !prevSelected.includes(spool));
 91        return select ? [...prevSelected, ...filtered] : filtered;
 92      });
 93    };
 94  
 95    // Handler for selecting/unselecting individual items
 96    const handleSelectItem = (item: number) => {
 97      setSelectedItems((prevSelected) =>
 98        prevSelected.includes(item) ? prevSelected.filter((selected) => selected !== item) : [...prevSelected, item],
 99      );
100    };
101  
102    // State for the select/unselect all checkbox
103    const isAllFilteredSelected = dataSource.every((spool) => selectedItems.includes(spool.id));
104    const isSomeButNotAllFilteredSelected =
105      dataSource.some((spool) => selectedItems.includes(spool.id)) && !isAllFilteredSelected;
106  
107    const commonProps = {
108      t,
109      navigate,
110      actions: () => {
111        return [];
112      },
113      dataSource,
114      tableState,
115      sorter: true,
116    };
117  
118    return (
119      <>
120        {contextHolder}
121        <Space direction="vertical" style={{ width: "100%" }}>
122          {description && <div>{description}</div>}
123          <Table
124            {...tableProps}
125            rowKey="id"
126            tableLayout="auto"
127            dataSource={dataSource}
128            pagination={false}
129            scroll={{ y: 200 }}
130            columns={removeUndefined([
131              {
132                width: 50,
133                render: (_, item: ISpool) => (
134                  <Checkbox checked={selectedItems.includes(item.id)} onChange={() => handleSelectItem(item.id)} />
135                ),
136              },
137              SortedColumn({
138                ...commonProps,
139                id: "id",
140                i18ncat: "spool",
141                width: 80,
142              }),
143              SpoolIconColumn({
144                ...commonProps,
145                id: "filament.combined_name",
146                dataId: "filament.combined_name",
147                i18nkey: "spool.fields.filament_name",
148                color: (record: ISpoolCollapsed) => record.filament.color_hex,
149                filterValueQuery: useSpoolmanFilamentFilter(),
150              }),
151              FilteredQueryColumn({
152                ...commonProps,
153                id: "filament.material",
154                i18nkey: "spool.fields.material",
155                filterValueQuery: useSpoolmanMaterials(),
156              }),
157            ])}
158          />
159          <Row gutter={[10, 10]}>
160            <Col span={12}>
161              <Checkbox
162                checked={isAllFilteredSelected}
163                indeterminate={isSomeButNotAllFilteredSelected}
164                onChange={(e) => {
165                  selectUnselectFiltered(e.target.checked);
166                }}
167              >
168                {t("printing.spoolSelect.selectAll")}
169              </Checkbox>
170            </Col>
171            <Col span={12}>
172              <div style={{ float: "right" }}>
173                {t("printing.spoolSelect.selectedTotal", {
174                  count: selectedItems.length,
175                })}
176              </div>
177            </Col>
178            <Col span={12}>
179              <Checkbox
180                checked={showArchived}
181                onChange={(e) => {
182                  setShowArchived(e.target.checked);
183                  if (!e.target.checked) {
184                    // Remove archived spools from selected items
185                    setSelectedItems((prevSelected) =>
186                      prevSelected.filter(
187                        (selected) => dataSource.find((spool) => spool.id === selected)?.archived !== true,
188                      ),
189                    );
190                  }
191                }}
192              >
193                {t("printing.spoolSelect.showArchived")}
194              </Checkbox>
195            </Col>
196            <Col span={24}>
197              <Button
198                type="primary"
199                icon={<RightOutlined />}
200                iconPosition="end"
201                onClick={() => {
202                  if (selectedItems.length === 0) {
203                    messageApi.open({
204                      type: "error",
205                      content: t("printing.spoolSelect.noSpoolsSelected"),
206                    });
207                    return;
208                  }
209                  onContinue(dataSource.filter((spool) => selectedItems.includes(spool.id)));
210                }}
211              >
212                {t("buttons.continue")}
213              </Button>
214            </Col>
215          </Row>
216        </Space>
217      </>
218    );
219  };
220  
221  export default SpoolSelectModal;