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;