Label printing page updates
No longer a modal Use flex divs instead of a table
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import { ISpool } from "../../pages/spools/model";
|
||||
import { useGetSetting, useSetSetting } from "../../utils/querySettings";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export interface PrintSettings {
|
||||
id: string;
|
||||
name?: string;
|
||||
margin?: { top: number; bottom: number; left: number; right: number };
|
||||
printerMargin?: { top: number; bottom: number; left: number; right: number };
|
||||
spacing?: { horizontal: number; vertical: number };
|
||||
columns?: number;
|
||||
rows?: number;
|
||||
skipItems?: number;
|
||||
itemCopies?: number;
|
||||
paperSize?: string;
|
||||
customPaperSize?: { width: number; height: number };
|
||||
borderShowMode?: "none" | "border" | "grid";
|
||||
}
|
||||
|
||||
export interface QRCodePrintSettings {
|
||||
showContent?: boolean;
|
||||
textSize?: number;
|
||||
showSpoolmanIcon?: boolean;
|
||||
printSettings: PrintSettings;
|
||||
}
|
||||
|
||||
export interface SpoolQRCodePrintSettings {
|
||||
template?: string;
|
||||
labelSettings: QRCodePrintSettings;
|
||||
}
|
||||
|
||||
export function useGetPrintSettings(): SpoolQRCodePrintSettings[] | undefined {
|
||||
const { data } = useGetSetting("print_settings");
|
||||
if (!data) return;
|
||||
const parsed: SpoolQRCodePrintSettings[] =
|
||||
data && data.value ? JSON.parse(data.value) : ([] as SpoolQRCodePrintSettings[]);
|
||||
// Loop through all parsed and generate a new ID field if it's not set
|
||||
return parsed.map((settings) => {
|
||||
if (!settings.labelSettings.printSettings.id) {
|
||||
settings.labelSettings.printSettings.id = uuidv4();
|
||||
}
|
||||
return settings;
|
||||
});
|
||||
}
|
||||
|
||||
export function useSetPrintSettings(): (spoolQRCodePrintSettings: SpoolQRCodePrintSettings[]) => void {
|
||||
const mut = useSetSetting("print_settings");
|
||||
|
||||
return (spoolQRCodePrintSettings: SpoolQRCodePrintSettings[]) => {
|
||||
mut.mutate(spoolQRCodePrintSettings);
|
||||
};
|
||||
}
|
||||
|
||||
interface GenericObject {
|
||||
[key: string]: any;
|
||||
extra: { [key: string]: string };
|
||||
}
|
||||
|
||||
function getTagValue(tag: string, obj: GenericObject): any {
|
||||
// Split tag by .
|
||||
const tagParts = tag.split(".");
|
||||
if (tagParts[0] === "extra") {
|
||||
return JSON.parse(obj.extra[tagParts[1]]);
|
||||
}
|
||||
|
||||
const value = obj[tagParts[0]] ?? "?";
|
||||
// check if value is itself an object. If so, recursively call this and remove the first part of the tag
|
||||
if (typeof value === "object") {
|
||||
return getTagValue(tagParts.slice(1).join("."), value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function applyNewline(text: string): JSX.Element[] {
|
||||
return text.split("\n").map((line, idx, arr) => (
|
||||
<span key={idx}>
|
||||
{line}
|
||||
{idx < arr.length - 1 && <br />}
|
||||
</span>
|
||||
));
|
||||
}
|
||||
|
||||
function applyTextFormatting(text: string): JSX.Element[] {
|
||||
const regex = /\*\*([\w\W]*?)\*\*/g;
|
||||
const parts = text.split(regex);
|
||||
// Map over the parts and wrap matched text with <b> tags
|
||||
const elements = parts.map((part, index) => {
|
||||
// Even index: outside asterisks, odd index: inside asterisks (to be bolded)
|
||||
const node = applyNewline(part);
|
||||
return index % 2 === 0 ? <span key={index}>{node}</span> : <b key={index}>{node}</b>;
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
export function renderLabelContents(template: string, spool: ISpool): JSX.Element {
|
||||
// Find all {tags} in the template string and loop over them
|
||||
let result = template.replace(/\{(.*?)\}/g, function (_, tag) {
|
||||
return getTagValue(tag, spool);
|
||||
});
|
||||
|
||||
// Split string on \n into individual lines
|
||||
return <>{applyTextFormatting(result)}</>;
|
||||
}
|
||||
@@ -1,793 +0,0 @@
|
||||
import React, { useRef } from "react";
|
||||
import {
|
||||
Modal,
|
||||
Slider,
|
||||
Button,
|
||||
Select,
|
||||
Row,
|
||||
Col,
|
||||
Form,
|
||||
Divider,
|
||||
RadioChangeEvent,
|
||||
Radio,
|
||||
InputNumber,
|
||||
Collapse,
|
||||
} from "antd";
|
||||
import ReactToPrint from "react-to-print";
|
||||
import { useSavedState } from "../../utils/saveload";
|
||||
import { useTranslate } from "@refinedev/core";
|
||||
import { PrintSettings } from "./printing";
|
||||
|
||||
interface PrintingDialogProps {
|
||||
items: JSX.Element[];
|
||||
printSettings: PrintSettings;
|
||||
setPrintSettings: (setPrintSettings: PrintSettings) => void;
|
||||
style?: string;
|
||||
extraSettings?: JSX.Element;
|
||||
extraSettingsStart?: JSX.Element;
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
interface PaperDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const paperDimensions: { [key: string]: PaperDimensions } = {
|
||||
A3: {
|
||||
width: 297,
|
||||
height: 420,
|
||||
},
|
||||
A4: {
|
||||
width: 210,
|
||||
height: 297,
|
||||
},
|
||||
A5: {
|
||||
width: 148,
|
||||
height: 210,
|
||||
},
|
||||
Letter: {
|
||||
width: 216,
|
||||
height: 279,
|
||||
},
|
||||
Legal: {
|
||||
width: 216,
|
||||
height: 356,
|
||||
},
|
||||
Tabloid: {
|
||||
width: 279,
|
||||
height: 432,
|
||||
},
|
||||
};
|
||||
|
||||
const PrintingDialog: React.FC<PrintingDialogProps> = ({
|
||||
items,
|
||||
printSettings,
|
||||
setPrintSettings,
|
||||
style,
|
||||
extraSettings,
|
||||
extraSettingsStart,
|
||||
visible,
|
||||
onCancel,
|
||||
title,
|
||||
}) => {
|
||||
const t = useTranslate();
|
||||
|
||||
const [collapseState, setCollapseState] = useSavedState<string[]>("print-collapseState", []);
|
||||
const [previewScale, setPreviewScale] = useSavedState("print-previewScale", 0.6);
|
||||
|
||||
const margin = printSettings?.margin || { top: 10, bottom: 10, left: 10, right: 10 };
|
||||
const printerMargin = printSettings?.printerMargin || { top: 5, bottom: 5, left: 5, right: 5 };
|
||||
const spacing = printSettings?.spacing || { horizontal: 0, vertical: 0 };
|
||||
const paperColumns = printSettings?.columns || 3;
|
||||
const paperRows = printSettings?.rows || 8;
|
||||
const skipItems = printSettings?.skipItems || 0;
|
||||
const itemCopies = printSettings?.itemCopies || 1;
|
||||
const paperSize = printSettings?.paperSize || "A4";
|
||||
const customPaperSize = printSettings?.customPaperSize || { width: 210, height: 297 };
|
||||
const borderShowMode = printSettings?.borderShowMode || "grid";
|
||||
|
||||
const paperWidth = paperSize === "custom" ? customPaperSize.width : paperDimensions[paperSize].width;
|
||||
const paperHeight = paperSize === "custom" ? customPaperSize.height : paperDimensions[paperSize].height;
|
||||
|
||||
const printRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const itemWidth = (paperWidth - margin.left - margin.right - spacing.horizontal) / paperColumns - spacing.horizontal;
|
||||
const itemHeight = (paperHeight - margin.top - margin.bottom - spacing.vertical) / paperRows - spacing.vertical;
|
||||
|
||||
const itemsPerRow = paperColumns;
|
||||
const rowsPerPage = paperRows;
|
||||
|
||||
const itemsIncludingSkipped = [...Array(skipItems).fill(<></>)];
|
||||
for (const item of items) {
|
||||
for (let i = 0; i < itemCopies; i += 1) {
|
||||
itemsIncludingSkipped.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
const rowsOfItems = [];
|
||||
for (let row_idx = 0; row_idx < itemsIncludingSkipped.length / itemsPerRow; row_idx += 1) {
|
||||
rowsOfItems.push(itemsIncludingSkipped.slice(row_idx * itemsPerRow, (row_idx + 1) * itemsPerRow));
|
||||
}
|
||||
|
||||
const pageBlocks = [];
|
||||
for (let page_idx = 0; page_idx < rowsOfItems.length / rowsPerPage; page_idx += 1) {
|
||||
pageBlocks.push(rowsOfItems.slice(page_idx * rowsPerPage, (page_idx + 1) * rowsPerPage));
|
||||
}
|
||||
|
||||
const pages = pageBlocks.map(function (rows, pageIdx) {
|
||||
const itemRows = rows.map((row, rowIdx) => {
|
||||
return (
|
||||
<tr key={rowIdx}>
|
||||
{row.map(function (item, colIdx) {
|
||||
return (
|
||||
<td key={colIdx}>
|
||||
<div
|
||||
style={{
|
||||
width: `${itemWidth}mm`,
|
||||
height: `${itemHeight}mm`,
|
||||
border: borderShowMode === "grid" ? "1px solid #000" : "none",
|
||||
paddingLeft: colIdx === 0 ? `${Math.max(printerMargin.left - margin.left, 0)}mm` : 0,
|
||||
paddingRight:
|
||||
colIdx === paperColumns - 1 ? `${Math.max(printerMargin.right - margin.right, 0)}mm` : 0,
|
||||
paddingTop: rowIdx === 0 ? `${Math.max(printerMargin.top - margin.top, 0)}mm` : 0,
|
||||
paddingBottom:
|
||||
rowIdx === paperRows - 1 ? `${Math.max(printerMargin.bottom - margin.bottom, 0)}mm` : 0,
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className="print-page"
|
||||
key={pageIdx}
|
||||
style={{
|
||||
width: `${paperWidth}mm`,
|
||||
height: `${paperHeight}mm`,
|
||||
backgroundColor: "#FFF",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="print-page-area"
|
||||
style={{
|
||||
border: borderShowMode !== "none" ? "1px solid #000" : "none",
|
||||
height: `${paperHeight - margin.top - margin.bottom}mm`,
|
||||
width: `${paperWidth - margin.left - margin.right}mm`,
|
||||
marginTop: `calc(${margin.top}mm - ${borderShowMode !== "none" ? "1px" : "0px"})`,
|
||||
marginLeft: `calc(${margin.left}mm - ${borderShowMode !== "none" ? "1px" : "0px"})`,
|
||||
marginRight: `calc(${margin.right}mm - ${borderShowMode !== "none" ? "1px" : "0px"})`,
|
||||
marginBottom: `calc(${margin.bottom}mm - ${borderShowMode !== "none" ? "1px" : "0px"})`,
|
||||
}}
|
||||
>
|
||||
<table
|
||||
style={{
|
||||
alignContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
{itemRows}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={visible}
|
||||
title={title ?? t("printing.generic.title")}
|
||||
onCancel={onCancel}
|
||||
footer={[
|
||||
<ReactToPrint
|
||||
key="print-button"
|
||||
trigger={() => <Button type="primary">{t("printing.generic.print")}</Button>}
|
||||
content={() => printRef.current}
|
||||
/>,
|
||||
]}
|
||||
width={1200} // Set the modal width to accommodate the preview
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={14}>
|
||||
{t("printing.generic.description")}
|
||||
<div
|
||||
style={{
|
||||
transform: "translateZ(0)",
|
||||
overflow: "hidden scroll",
|
||||
height: "70vh",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="print-container"
|
||||
ref={printRef}
|
||||
style={{
|
||||
transform: `scale(${previewScale})`,
|
||||
transformOrigin: "top left",
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
<style>
|
||||
{`
|
||||
@media print {
|
||||
@page {
|
||||
size: auto;
|
||||
margin: 0;
|
||||
}
|
||||
.print-container {
|
||||
transform: scale(1) !important;
|
||||
}
|
||||
.print-page {
|
||||
page-break-before: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen {
|
||||
.print-page {
|
||||
margin-top: 10mm;
|
||||
}
|
||||
}
|
||||
|
||||
.print-page table {
|
||||
border-spacing: ${spacing.horizontal}mm ${spacing.vertical}mm;
|
||||
border-collapse: separate;
|
||||
}
|
||||
|
||||
.print-page td {
|
||||
padding: 0;
|
||||
}
|
||||
${style ?? ""}
|
||||
`}
|
||||
</style>
|
||||
{pages}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<Form labelAlign="left" colon={false} labelWrap={true} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}>
|
||||
{extraSettingsStart}
|
||||
<Divider />
|
||||
<Form.Item label={t("printing.generic.skipItems")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={30}
|
||||
value={skipItems}
|
||||
onChange={(value) => {
|
||||
printSettings.skipItems = value;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={0}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={skipItems}
|
||||
onChange={(value) => {
|
||||
printSettings.skipItems = value ?? 1;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.itemCopies")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={1}
|
||||
max={3}
|
||||
value={itemCopies}
|
||||
onChange={(value) => {
|
||||
printSettings.itemCopies = value;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={itemCopies}
|
||||
onChange={(value) => {
|
||||
printSettings.itemCopies = value ?? 1;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.showBorder")}>
|
||||
<Radio.Group
|
||||
options={[
|
||||
{ label: t("printing.generic.borders.none"), value: "none" },
|
||||
{
|
||||
label: t("printing.generic.borders.border"),
|
||||
value: "border",
|
||||
},
|
||||
{ label: t("printing.generic.borders.grid"), value: "grid" },
|
||||
]}
|
||||
onChange={(e: RadioChangeEvent) => {
|
||||
printSettings.borderShowMode = e.target.value;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
value={borderShowMode}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.previewScale")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0.1}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={previewScale}
|
||||
onChange={(value) => {
|
||||
setPreviewScale(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={0.1}
|
||||
max={1}
|
||||
step={0.01}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={previewScale}
|
||||
onChange={(value) => {
|
||||
setPreviewScale(value ?? 0.1);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Divider />
|
||||
<Collapse
|
||||
defaultActiveKey={collapseState}
|
||||
bordered={false}
|
||||
ghost
|
||||
onChange={(key) => {
|
||||
if (Array.isArray(key)) {
|
||||
setCollapseState(key);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Collapse.Panel header={t("printing.generic.contentSettings")} key="1">
|
||||
{extraSettings}
|
||||
</Collapse.Panel>
|
||||
<Collapse.Panel header={t("printing.generic.layoutSettings")} key="2">
|
||||
<Form.Item label={t("printing.generic.paperSize")}>
|
||||
<Select
|
||||
value={paperSize}
|
||||
onChange={(value) => {
|
||||
printSettings.paperSize = value;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
>
|
||||
{Object.keys(paperDimensions).map((key) => (
|
||||
<Select.Option key={key} value={key}>
|
||||
{key}
|
||||
</Select.Option>
|
||||
))}
|
||||
<Select.Option value="custom">{t("printing.generic.customSize")}</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.dimensions")} hidden={paperSize !== "custom"}>
|
||||
<Row align="middle">
|
||||
<Col span={11}>
|
||||
<InputNumber
|
||||
value={customPaperSize.width}
|
||||
min={0.1}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
customPaperSize.width = value ?? 0;
|
||||
printSettings.customPaperSize = customPaperSize;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={2} style={{ textAlign: "center" }}>
|
||||
x
|
||||
</Col>
|
||||
<Col span={11}>
|
||||
<InputNumber
|
||||
value={customPaperSize.height}
|
||||
min={0.1}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
customPaperSize.height = value ?? 0;
|
||||
printSettings.customPaperSize = customPaperSize;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.columns")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={1}
|
||||
max={5}
|
||||
value={paperColumns}
|
||||
onChange={(value) => {
|
||||
printSettings.columns = value;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={paperColumns}
|
||||
onChange={(value) => {
|
||||
printSettings.columns = value ?? 1;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.rows")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={1}
|
||||
max={15}
|
||||
value={paperRows}
|
||||
onChange={(value) => {
|
||||
printSettings.rows = value;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={paperRows}
|
||||
onChange={(value) => {
|
||||
printSettings.rows = value ?? 1;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Divider />
|
||||
<p>{t("printing.generic.helpMargin")}</p>
|
||||
<Form.Item label={t("printing.generic.marginLeft")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={-20}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={margin.left}
|
||||
onChange={(value) => {
|
||||
margin.left = value;
|
||||
printSettings.margin = margin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={margin.left}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
margin.left = value ?? 0;
|
||||
printSettings.margin = margin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.marginTop")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={-20}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={margin.top}
|
||||
onChange={(value) => {
|
||||
margin.top = value;
|
||||
printSettings.margin = margin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={margin.top}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
margin.top = value ?? 0;
|
||||
printSettings.margin = margin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.marginRight")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={-20}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={margin.right}
|
||||
onChange={(value) => {
|
||||
margin.right = value;
|
||||
printSettings.margin = margin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={margin.right}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
margin.right = value ?? 0;
|
||||
printSettings.margin = margin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.marginBottom")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={-20}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={margin.bottom}
|
||||
onChange={(value) => {
|
||||
margin.bottom = value;
|
||||
printSettings.margin = margin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={margin.bottom}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
margin.bottom = value ?? 0;
|
||||
printSettings.margin = margin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Divider />
|
||||
<p>{t("printing.generic.helpPrinterMargin")}</p>
|
||||
<Form.Item label={t("printing.generic.printerMarginLeft")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={printerMargin.left}
|
||||
onChange={(value) => {
|
||||
printerMargin.left = value;
|
||||
printSettings.printerMargin = printerMargin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={printerMargin.left}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
printerMargin.left = value ?? 0;
|
||||
printSettings.printerMargin = printerMargin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.printerMarginTop")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={printerMargin.top}
|
||||
onChange={(value) => {
|
||||
printerMargin.top = value;
|
||||
printSettings.printerMargin = printerMargin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={printerMargin.top}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
printerMargin.top = value ?? 0;
|
||||
printSettings.printerMargin = printerMargin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.printerMarginRight")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={printerMargin.right}
|
||||
onChange={(value) => {
|
||||
printerMargin.right = value;
|
||||
printSettings.printerMargin = printerMargin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={printerMargin.right}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
printerMargin.right = value ?? 0;
|
||||
printSettings.printerMargin = printerMargin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.printerMarginBottom")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={printerMargin.bottom}
|
||||
onChange={(value) => {
|
||||
printerMargin.bottom = value;
|
||||
printSettings.printerMargin = printerMargin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={printerMargin.bottom}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
printerMargin.bottom = value ?? 0;
|
||||
printSettings.printerMargin = printerMargin;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Divider />
|
||||
<Form.Item label={t("printing.generic.horizontalSpacing")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={spacing.horizontal}
|
||||
onChange={(value) => {
|
||||
spacing.horizontal = value;
|
||||
printSettings.spacing = spacing;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={spacing.horizontal}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
spacing.horizontal = value ?? 0;
|
||||
printSettings.spacing = spacing;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.verticalSpacing")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={spacing.vertical}
|
||||
onChange={(value) => {
|
||||
spacing.vertical = value;
|
||||
printSettings.spacing = spacing;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={spacing.vertical}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
spacing.vertical = value ?? 0;
|
||||
printSettings.spacing = spacing;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrintingDialog;
|
||||
@@ -1,161 +0,0 @@
|
||||
import { Col, Form, InputNumber, QRCode, Row, Slider, Switch } from "antd";
|
||||
import { useSavedState } from "../../utils/saveload";
|
||||
import PrintingDialog from "./printingDialog";
|
||||
import { useTranslate } from "@refinedev/core";
|
||||
import { QRCodePrintSettings } from "./printing";
|
||||
|
||||
interface QRCodeData {
|
||||
value: string;
|
||||
label?: JSX.Element;
|
||||
errorLevel?: "L" | "M" | "Q" | "H";
|
||||
}
|
||||
|
||||
interface QRCodePrintingDialogProps {
|
||||
visible: boolean;
|
||||
items: QRCodeData[];
|
||||
printSettings: QRCodePrintSettings;
|
||||
setPrintSettings: (setPrintSettings: QRCodePrintSettings) => void;
|
||||
onCancel: () => void;
|
||||
extraSettings?: JSX.Element;
|
||||
extraSettingsStart?: JSX.Element;
|
||||
}
|
||||
|
||||
const QRCodePrintingDialog: React.FC<QRCodePrintingDialogProps> = ({
|
||||
visible,
|
||||
items,
|
||||
printSettings,
|
||||
setPrintSettings,
|
||||
onCancel,
|
||||
extraSettings,
|
||||
extraSettingsStart,
|
||||
}) => {
|
||||
const t = useTranslate();
|
||||
|
||||
const showContent = printSettings?.showContent === undefined ? true : printSettings?.showContent;
|
||||
const textSize = printSettings?.textSize || 5;
|
||||
const showSpoolmanIcon = printSettings?.showSpoolmanIcon === undefined ? true : printSettings?.showSpoolmanIcon;
|
||||
|
||||
const elements = items.map((item) => {
|
||||
return (
|
||||
<div className="print-qrcode-item">
|
||||
<div className="print-qrcode-container">
|
||||
<QRCode
|
||||
className="print-qrcode"
|
||||
icon={showSpoolmanIcon ? "/favicon.svg" : undefined}
|
||||
value={item.value}
|
||||
errorLevel={item.errorLevel}
|
||||
type="svg"
|
||||
color="#000"
|
||||
/>
|
||||
</div>
|
||||
{showContent && <div className="print-qrcode-title">{item.label ?? item.value}</div>}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<PrintingDialog
|
||||
visible={visible}
|
||||
title={t("printing.qrcode.title")}
|
||||
items={elements}
|
||||
printSettings={printSettings.printSettings}
|
||||
setPrintSettings={(newSettings) => {
|
||||
printSettings.printSettings = newSettings;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
extraSettingsStart={extraSettingsStart}
|
||||
extraSettings={
|
||||
<>
|
||||
<Form.Item label={t("printing.qrcode.showContent")}>
|
||||
<Switch
|
||||
checked={showContent}
|
||||
onChange={(checked) => {
|
||||
printSettings.showContent = checked;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.qrcode.textSize")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
disabled={!showContent}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
min={2}
|
||||
max={7}
|
||||
value={textSize}
|
||||
step={0.1}
|
||||
onChange={(value) => {
|
||||
printSettings.textSize = value;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
disabled={!showContent}
|
||||
min={0.01}
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={textSize}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
printSettings.textSize = value ?? 5;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.qrcode.showSpoolmanIcon")}>
|
||||
<Switch
|
||||
checked={showSpoolmanIcon}
|
||||
onChange={(checked) => {
|
||||
printSettings.showSpoolmanIcon = checked;
|
||||
setPrintSettings(printSettings);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
{extraSettings}
|
||||
</>
|
||||
}
|
||||
style={`
|
||||
.print-page .print-qrcode-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.print-page .print-qrcode-container {
|
||||
max-width: ${showContent ? "50%" : "100%"};
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.print-page .print-qrcode {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.print-page .print-qrcode-title {
|
||||
flex: 1 1 auto;
|
||||
font-size: ${textSize}mm;
|
||||
color: #000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.print-page canvas, .print-page svg {
|
||||
/* display: block; */
|
||||
object-fit: contain;
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
`}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRCodePrintingDialog;
|
||||
@@ -1,322 +0,0 @@
|
||||
import { Button, Flex, Form, Input, Modal, Popconfirm, Select, Space, Switch, Table, Typography } from "antd";
|
||||
import { IFilament } from "../../pages/filaments/model";
|
||||
import { ISpool } from "../../pages/spools/model";
|
||||
import QRCodePrintingDialog from "./qrCodePrintingDialog";
|
||||
import { useSavedState } from "../../utils/saveload";
|
||||
import { useTranslate } from "@refinedev/core";
|
||||
import {
|
||||
QRCodePrintSettings,
|
||||
SpoolQRCodePrintSettings,
|
||||
renderLabelContents,
|
||||
useGetPrintSettings,
|
||||
useSetPrintSettings,
|
||||
} from "./printing";
|
||||
import { useMemo, useState } from "react";
|
||||
import { DeleteOutlined, EditOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import _ from "lodash";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import { EntityType, useGetFields } from "../../utils/queryFields";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface SpoolQRCodePrintingDialog {
|
||||
visible: boolean;
|
||||
items: ISpool[];
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const SpoolQRCodePrintingDialog: React.FC<SpoolQRCodePrintingDialog> = ({ visible, items, onCancel }) => {
|
||||
const t = useTranslate();
|
||||
|
||||
// Selected setting state
|
||||
const [selectedSetting, setSelectedSetting] = useState<string | undefined>();
|
||||
|
||||
// Keep a local copy of the settings which is what's actually displayed. Use the remote state only for saving.
|
||||
// This decouples the debounce stuff from the UI
|
||||
const [localSettings, setLocalSettings] = useState<SpoolQRCodePrintSettings[] | undefined>();
|
||||
const remoteSettings = useGetPrintSettings();
|
||||
const setRemoteSettings = useSetPrintSettings();
|
||||
const debouncedSetRemoteSettings = useMemo(() => _.debounce(setRemoteSettings, 500), []);
|
||||
|
||||
const allPrintSettings = localSettings ?? remoteSettings;
|
||||
const setPrintSettings = (newSettings: SpoolQRCodePrintSettings[]) => {
|
||||
setLocalSettings(newSettings);
|
||||
debouncedSetRemoteSettings(newSettings);
|
||||
};
|
||||
|
||||
// Functions to update settings
|
||||
const addNewPrintSettings = () => {
|
||||
if (!allPrintSettings) return;
|
||||
const newId = uuidv4();
|
||||
const newSetting = {
|
||||
labelSettings: {
|
||||
printSettings: {
|
||||
id: newId,
|
||||
name: t("printing.generic.newSetting"),
|
||||
},
|
||||
},
|
||||
};
|
||||
setPrintSettings([...allPrintSettings, newSetting]);
|
||||
setSelectedSetting(newId);
|
||||
return newSetting;
|
||||
};
|
||||
const updateCurrentPrintSettings = (newSettings: SpoolQRCodePrintSettings) => {
|
||||
if (!allPrintSettings) return;
|
||||
setPrintSettings(
|
||||
allPrintSettings.map((settings) =>
|
||||
settings.labelSettings.printSettings.id === newSettings.labelSettings.printSettings.id ? newSettings : settings
|
||||
)
|
||||
);
|
||||
};
|
||||
const deleteCurrentPrintSettings = () => {
|
||||
if (!allPrintSettings) return;
|
||||
setPrintSettings(
|
||||
allPrintSettings.filter((qSetting) => qSetting.labelSettings.printSettings.id !== selectedSetting)
|
||||
);
|
||||
setSelectedSetting(undefined);
|
||||
};
|
||||
|
||||
// Initialize settings
|
||||
let selectedPrintSetting: SpoolQRCodePrintSettings;
|
||||
if (allPrintSettings === undefined) {
|
||||
// DB not loaded yet, use a temporary one
|
||||
selectedPrintSetting = {
|
||||
labelSettings: {
|
||||
printSettings: {
|
||||
id: "TEMP",
|
||||
name: t("printing.generic.newSetting"),
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// DB is loaded, find the selected setting
|
||||
if (allPrintSettings.length === 0) {
|
||||
// DB loaded, but no settings found, add a new one and select it
|
||||
const newSetting = addNewPrintSettings();
|
||||
if (!newSetting) {
|
||||
console.error("Error adding new setting, this should never happen");
|
||||
return;
|
||||
}
|
||||
|
||||
// Mutate the allPrintSettings list so that the rest of the UI will work fine
|
||||
allPrintSettings.push(newSetting);
|
||||
selectedPrintSetting = newSetting;
|
||||
} else {
|
||||
// DB loaded and at least 1 setting exists
|
||||
if (!selectedSetting) {
|
||||
// No setting has been selected, select the first one
|
||||
selectedPrintSetting = allPrintSettings[0];
|
||||
setSelectedSetting(allPrintSettings[0].labelSettings.printSettings.id);
|
||||
} else {
|
||||
// A setting has been selected, find it
|
||||
const foundSetting = allPrintSettings.find(
|
||||
(settings) => settings.labelSettings.printSettings.id === selectedSetting
|
||||
);
|
||||
if (foundSetting) {
|
||||
selectedPrintSetting = foundSetting;
|
||||
} else {
|
||||
// Selected setting not found, select a temp one
|
||||
selectedPrintSetting = {
|
||||
labelSettings: {
|
||||
printSettings: {
|
||||
id: "TEMP",
|
||||
name: t("printing.generic.newSetting"),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [templateHelpOpen, setTemplateHelpOpen] = useState(false);
|
||||
const template =
|
||||
selectedPrintSetting.template ??
|
||||
`**{filament.vendor.name} - {filament.name}
|
||||
#{id} - {filament.material}**
|
||||
Spool Weight: {filament.spool_weight} g
|
||||
ET: {filament.settings_extruder_temp} °C
|
||||
BT: {filament.settings_bed_temp} °C
|
||||
Lot Nr: {lot_nr}
|
||||
{comment}
|
||||
{filament.comment}
|
||||
{filament.vendor.comment}`;
|
||||
|
||||
const spoolTags = [
|
||||
{ tag: "id" },
|
||||
{ tag: "registered" },
|
||||
{ tag: "first_used" },
|
||||
{ tag: "last_used" },
|
||||
{ tag: "price" },
|
||||
{ tag: "initial_weight" },
|
||||
{ tag: "spool_weight" },
|
||||
{ tag: "remaining_weight" },
|
||||
{ tag: "used_weight" },
|
||||
{ tag: "remaining_length" },
|
||||
{ tag: "used_length" },
|
||||
{ tag: "location" },
|
||||
{ tag: "lot_nr" },
|
||||
{ tag: "comment" },
|
||||
{ tag: "archived" },
|
||||
];
|
||||
const spoolFields = useGetFields(EntityType.spool);
|
||||
if (spoolFields.data !== undefined) {
|
||||
spoolFields.data.forEach((field) => {
|
||||
spoolTags.push({ tag: `extra.${field.key}` });
|
||||
});
|
||||
}
|
||||
const filamentTags = [
|
||||
{ tag: "filament.id" },
|
||||
{ tag: "filament.registered" },
|
||||
{ tag: "filament.name" },
|
||||
{ tag: "filament.material" },
|
||||
{ tag: "filament.price" },
|
||||
{ tag: "filament.density" },
|
||||
{ tag: "filament.diameter" },
|
||||
{ tag: "filament.weight" },
|
||||
{ tag: "filament.spool_weight" },
|
||||
{ tag: "filament.article_number" },
|
||||
{ tag: "filament.comment" },
|
||||
{ tag: "filament.settings_extruder_temp" },
|
||||
{ tag: "filament.settings_bed_temp" },
|
||||
{ tag: "filament.color_hex" },
|
||||
{ tag: "filament.multi_color_hexes" },
|
||||
{ tag: "filament.multi_color_direction" },
|
||||
{ tag: "filament.external_id" },
|
||||
];
|
||||
const filamentFields = useGetFields(EntityType.filament);
|
||||
if (filamentFields.data !== undefined) {
|
||||
filamentFields.data.forEach((field) => {
|
||||
filamentTags.push({ tag: `filament.extra.${field.key}` });
|
||||
});
|
||||
}
|
||||
const vendorTags = [
|
||||
{ tag: "filament.vendor.id" },
|
||||
{ tag: "filament.vendor.registered" },
|
||||
{ tag: "filament.vendor.name" },
|
||||
{ tag: "filament.vendor.comment" },
|
||||
{ tag: "filament.vendor.empty_spool_weight" },
|
||||
{ tag: "filament.vendor.external_id" },
|
||||
];
|
||||
const vendorFields = useGetFields(EntityType.vendor);
|
||||
if (vendorFields.data !== undefined) {
|
||||
vendorFields.data.forEach((field) => {
|
||||
vendorTags.push({ tag: `filament.vendor.extra.${field.key}` });
|
||||
});
|
||||
}
|
||||
|
||||
const templateTags = [...spoolTags, ...filamentTags, ...vendorTags];
|
||||
|
||||
return (
|
||||
<QRCodePrintingDialog
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
printSettings={selectedPrintSetting.labelSettings}
|
||||
setPrintSettings={(newSettings) => {
|
||||
selectedPrintSetting.labelSettings = newSettings;
|
||||
updateCurrentPrintSettings(selectedPrintSetting);
|
||||
}}
|
||||
extraSettingsStart={
|
||||
<>
|
||||
<Form.Item label={t("printing.generic.settings")}>
|
||||
<Flex gap={8}>
|
||||
<Select
|
||||
value={selectedSetting}
|
||||
onChange={(value) => {
|
||||
setSelectedSetting(value);
|
||||
}}
|
||||
options={
|
||||
allPrintSettings &&
|
||||
allPrintSettings.map((settings) => ({
|
||||
label: settings.labelSettings.printSettings?.name || t("printing.generic.defaultSettings"),
|
||||
value: settings.labelSettings.printSettings.id,
|
||||
}))
|
||||
}
|
||||
></Select>
|
||||
<Button
|
||||
style={{ width: "3em" }}
|
||||
icon={<PlusOutlined />}
|
||||
title={t("printing.generic.addSettings")}
|
||||
onClick={addNewPrintSettings}
|
||||
/>
|
||||
{allPrintSettings && allPrintSettings.length > 1 && (
|
||||
<Popconfirm
|
||||
title={t("printing.generic.deleteSettings")}
|
||||
description={t("printing.generic.deleteSettingsConfirm")}
|
||||
onConfirm={deleteCurrentPrintSettings}
|
||||
okText={t("buttons.delete")}
|
||||
cancelText={t("buttons.cancel")}
|
||||
>
|
||||
<Button
|
||||
style={{ width: "3em" }}
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
title={t("printing.generic.deleteSettings")}
|
||||
/>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.settingsName")}>
|
||||
<Input
|
||||
value={selectedPrintSetting.labelSettings.printSettings?.name}
|
||||
onChange={(e) => {
|
||||
selectedPrintSetting.labelSettings.printSettings.name = e.target.value;
|
||||
updateCurrentPrintSettings(selectedPrintSetting);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
}
|
||||
items={items.map(function (spool) {
|
||||
return {
|
||||
value: `web+spoolman:s-${spool.id}`,
|
||||
label: (
|
||||
<p
|
||||
style={{
|
||||
padding: "1mm 1mm 1mm 0",
|
||||
whiteSpace: "pre-wrap",
|
||||
}}
|
||||
>
|
||||
{renderLabelContents(template, spool)}
|
||||
</p>
|
||||
),
|
||||
errorLevel: "H",
|
||||
};
|
||||
})}
|
||||
extraSettings={
|
||||
<>
|
||||
<Form.Item label={t("printing.qrcode.template")}>
|
||||
<TextArea
|
||||
value={template}
|
||||
rows={8}
|
||||
onChange={(newValue) => {
|
||||
selectedPrintSetting.template = newValue.target.value;
|
||||
updateCurrentPrintSettings(selectedPrintSetting);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Modal open={templateHelpOpen} footer={null} onCancel={() => setTemplateHelpOpen(false)}>
|
||||
<Table
|
||||
size="small"
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
scroll={{ y: 400 }}
|
||||
columns={[{ dataIndex: "tag" }]}
|
||||
dataSource={templateTags}
|
||||
/>
|
||||
</Modal>
|
||||
<Text type="secondary">
|
||||
{t("printing.qrcode.templateHelp")}{" "}
|
||||
<Button size="small" onClick={() => setTemplateHelpOpen(true)}>
|
||||
{t("actions.show")}
|
||||
</Button>
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpoolQRCodePrintingDialog;
|
||||
@@ -1,50 +0,0 @@
|
||||
// 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 { Button } from "antd";
|
||||
import { ISpool } from "../pages/spools/model";
|
||||
import { PrinterOutlined } from "@ant-design/icons";
|
||||
import { useTranslate } from "@refinedev/core";
|
||||
import SpoolQRCodePrintingDialog from "./printing/spoolQrCodePrintingDialog";
|
||||
|
||||
const SelectAndPrint: React.FC = () => {
|
||||
const t = useTranslate();
|
||||
|
||||
const [step, setStep] = React.useState(0);
|
||||
const [selectedSpools, setSelectedSpools] = React.useState<ISpool[]>([]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PrinterOutlined />}
|
||||
onClick={() => {
|
||||
setStep(1);
|
||||
}}
|
||||
>
|
||||
{t("printing.qrcode.button")}
|
||||
</Button>
|
||||
<SpoolSelectModal
|
||||
visible={step === 1}
|
||||
description={t("printing.spoolSelect.description")}
|
||||
onCancel={() => {
|
||||
setStep(0);
|
||||
}}
|
||||
onContinue={(spools) => {
|
||||
setSelectedSpools(spools);
|
||||
setStep(2);
|
||||
}}
|
||||
/>
|
||||
<SpoolQRCodePrintingDialog
|
||||
visible={step === 2}
|
||||
onCancel={() => {
|
||||
setStep(0);
|
||||
}}
|
||||
items={selectedSpools}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectAndPrint;
|
||||
@@ -1,217 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { Modal, Table, Checkbox, Space, Row, Col, message } from "antd";
|
||||
import { ISpool } from "../pages/spools/model";
|
||||
import { FilteredQueryColumn, SortedColumn, SpoolIconColumn } from "./column";
|
||||
import { TableState } from "../utils/saveload";
|
||||
import { useTable } from "@refinedev/antd";
|
||||
import { t } from "i18next";
|
||||
import { useSpoolmanFilamentFilter, useSpoolmanMaterials } from "./otherModels";
|
||||
import { removeUndefined } from "../utils/filtering";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
description?: string;
|
||||
onCancel: () => void;
|
||||
onContinue: (selectedSpools: ISpool[]) => void;
|
||||
}
|
||||
|
||||
interface ISpoolCollapsed extends ISpool {
|
||||
"filament.combined_name": string;
|
||||
"filament.id": number;
|
||||
"filament.material"?: string;
|
||||
}
|
||||
|
||||
function collapseSpool(element: ISpool): ISpoolCollapsed {
|
||||
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.combined_name": filament_name,
|
||||
"filament.id": element.filament.id,
|
||||
"filament.material": element.filament.material,
|
||||
};
|
||||
}
|
||||
|
||||
const SpoolSelectModal: React.FC<Props> = ({ visible, description, onCancel, onContinue }) => {
|
||||
const [selectedItems, setSelectedItems] = useState<number[]>([]);
|
||||
const [showArchived, setShowArchived] = useState(false);
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { tableProps, sorters, filters, current, pageSize } = useTable<ISpoolCollapsed>({
|
||||
meta: {
|
||||
queryParams: {
|
||||
["allow_archived"]: showArchived,
|
||||
},
|
||||
},
|
||||
syncWithLocation: false,
|
||||
pagination: {
|
||||
mode: "off",
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
sorters: {
|
||||
mode: "server",
|
||||
},
|
||||
filters: {
|
||||
mode: "server",
|
||||
},
|
||||
queryOptions: {
|
||||
select(data) {
|
||||
return {
|
||||
total: data.total,
|
||||
data: data.data.map(collapseSpool),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 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((record) => ({ ...record })),
|
||||
[tableProps.dataSource]
|
||||
);
|
||||
|
||||
// Function to add/remove all filtered items from selected items
|
||||
const selectUnselectFiltered = (select: boolean) => {
|
||||
setSelectedItems((prevSelected) => {
|
||||
const filtered = dataSource.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 = dataSource.every((spool) => selectedItems.includes(spool.id));
|
||||
const isSomeButNotAllFilteredSelected =
|
||||
dataSource.some((spool) => selectedItems.includes(spool.id)) && !isAllFilteredSelected;
|
||||
|
||||
const commonProps = {
|
||||
t,
|
||||
navigate,
|
||||
actions: () => {
|
||||
return [];
|
||||
},
|
||||
dataSource,
|
||||
tableState,
|
||||
sorter: true,
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("printing.spoolSelect.title")}
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={() => {
|
||||
if (selectedItems.length === 0) {
|
||||
messageApi.open({
|
||||
type: "error",
|
||||
content: t("printing.spoolSelect.noSpoolsSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
onContinue(dataSource.filter((spool) => selectedItems.includes(spool.id)));
|
||||
}}
|
||||
width={600}
|
||||
>
|
||||
{contextHolder}
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
{description && <div>{description}</div>}
|
||||
<Table
|
||||
{...tableProps}
|
||||
rowKey="id"
|
||||
tableLayout="auto"
|
||||
dataSource={dataSource}
|
||||
pagination={false}
|
||||
scroll={{ y: 200 }}
|
||||
columns={removeUndefined([
|
||||
{
|
||||
width: 50,
|
||||
render: (_, item: ISpool) => (
|
||||
<Checkbox checked={selectedItems.includes(item.id)} onChange={() => handleSelectItem(item.id)} />
|
||||
),
|
||||
},
|
||||
SortedColumn({
|
||||
...commonProps,
|
||||
id: "id",
|
||||
i18ncat: "spool",
|
||||
width: 80,
|
||||
}),
|
||||
SpoolIconColumn({
|
||||
...commonProps,
|
||||
id: "filament.combined_name",
|
||||
dataId: "filament.combined_name",
|
||||
i18nkey: "spool.fields.filament_name",
|
||||
color: (record: ISpoolCollapsed) => record.filament.color_hex,
|
||||
filterValueQuery: useSpoolmanFilamentFilter(),
|
||||
}),
|
||||
FilteredQueryColumn({
|
||||
...commonProps,
|
||||
id: "filament.material",
|
||||
i18nkey: "spool.fields.material",
|
||||
filterValueQuery: useSpoolmanMaterials(),
|
||||
}),
|
||||
])}
|
||||
/>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Checkbox
|
||||
checked={isAllFilteredSelected}
|
||||
indeterminate={isSomeButNotAllFilteredSelected}
|
||||
onChange={(e) => {
|
||||
selectUnselectFiltered(e.target.checked);
|
||||
}}
|
||||
>
|
||||
{t("printing.spoolSelect.selectAll")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div style={{ float: "right" }}>
|
||||
{t("printing.spoolSelect.selectedTotal", {
|
||||
count: selectedItems.length,
|
||||
})}
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Checkbox
|
||||
checked={showArchived}
|
||||
onChange={(e) => {
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("printing.spoolSelect.showArchived")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
</Space>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpoolSelectModal;
|
||||
Reference in New Issue
Block a user