Label printing page updates

No longer a modal
Use flex divs instead of a table
This commit is contained in:
Donkie
2024-08-04 23:23:13 +02:00
parent 9aa9479a14
commit 9318ef5c19
9 changed files with 154 additions and 173 deletions

View File

@@ -192,6 +192,7 @@ function App() {
/> />
<Route path="edit/:id" element={<LoadableResourcePage resource="spools" page="edit" />} /> <Route path="edit/:id" element={<LoadableResourcePage resource="spools" page="edit" />} />
<Route path="show/:id" element={<LoadableResourcePage resource="spools" page="show" />} /> <Route path="show/:id" element={<LoadableResourcePage resource="spools" page="show" />} />
<Route path="print" element={<LoadablePage name="printing" />} />
</Route> </Route>
<Route path="/filament"> <Route path="/filament">
<Route index element={<LoadableResourcePage resource="filaments" page="list" />} /> <Route index element={<LoadableResourcePage resource="filaments" page="list" />} />

View File

@@ -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;

View File

@@ -0,0 +1,50 @@
import React from "react";
import { IResourceComponentsProps, useTranslate } from "@refinedev/core";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { Content } from "antd/es/layout/layout";
import { theme } from "antd";
import SpoolQRCodePrintingDialog from "./spoolQrCodePrintingDialog";
import SpoolSelectModal from "./spoolSelectModal";
import { ISpool } from "../spools/model";
dayjs.extend(utc);
const { useToken } = theme;
export const Printing: React.FC<IResourceComponentsProps> = () => {
const { token } = useToken();
const t = useTranslate();
const [step, setStep] = React.useState<0 | 1>(0);
const [selectedSpools, setSelectedSpools] = React.useState<ISpool[]>([]);
return (
<Content
style={{
padding: 20,
minHeight: 280,
margin: "0 auto",
backgroundColor: token.colorBgContainer,
borderRadius: token.borderRadiusLG,
color: token.colorText,
fontFamily: token.fontFamily,
fontSize: token.fontSizeLG,
lineHeight: 1.5,
}}
>
{step === 0 && (
<SpoolSelectModal
description={t("printing.spoolSelect.description")}
onContinue={(spools) => {
setSelectedSpools(spools);
setStep(1);
}}
/>
)}
{step === 1 && <SpoolQRCodePrintingDialog items={selectedSpools} />}
</Content>
);
};
export default Printing;

View File

@@ -1,5 +1,5 @@
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { ISpool } from "../../pages/spools/model"; import { ISpool } from "../spools/model";
import { useGetSetting, useSetSetting } from "../../utils/querySettings"; import { useGetSetting, useSetSetting } from "../../utils/querySettings";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";

View File

@@ -25,9 +25,6 @@ interface PrintingDialogProps {
style?: string; style?: string;
extraSettings?: JSX.Element; extraSettings?: JSX.Element;
extraSettingsStart?: JSX.Element; extraSettingsStart?: JSX.Element;
visible: boolean;
onCancel: () => void;
title?: string;
} }
interface PaperDimensions { interface PaperDimensions {
@@ -69,9 +66,6 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
style, style,
extraSettings, extraSettings,
extraSettingsStart, extraSettingsStart,
visible,
onCancel,
title,
}) => { }) => {
const t = useTranslate(); const t = useTranslate();
@@ -98,7 +92,7 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
const itemHeight = (paperHeight - margin.top - margin.bottom - spacing.vertical) / paperRows - spacing.vertical; const itemHeight = (paperHeight - margin.top - margin.bottom - spacing.vertical) / paperRows - spacing.vertical;
const itemsPerRow = paperColumns; const itemsPerRow = paperColumns;
const rowsPerPage = paperRows; const itemsPerPage = itemsPerRow * paperRows;
const itemsIncludingSkipped = [...Array(skipItems).fill(<></>)]; const itemsIncludingSkipped = [...Array(skipItems).fill(<></>)];
for (const item of items) { for (const item of items) {
@@ -107,42 +101,33 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
} }
} }
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 = []; const pageBlocks = [];
for (let page_idx = 0; page_idx < rowsOfItems.length / rowsPerPage; page_idx += 1) { for (let page_idx = 0; page_idx < itemsIncludingSkipped.length / itemsPerPage; page_idx += 1) {
pageBlocks.push(rowsOfItems.slice(page_idx * rowsPerPage, (page_idx + 1) * rowsPerPage)); pageBlocks.push(itemsIncludingSkipped.slice(page_idx * itemsPerPage, (page_idx + 1) * itemsPerPage));
} }
const pages = pageBlocks.map(function (rows, pageIdx) { const pages = pageBlocks.map(function (items, pageIdx) {
const itemRows = rows.map((row, rowIdx) => { const itemDivs = items.map((item, itemIdx) => {
const isFirstColumn = itemIdx % itemsPerRow === 0;
const isLastColumn = (itemIdx + 1) % itemsPerRow === 0;
const isFirstRow = itemIdx < itemsPerRow;
const isLastRow = itemsPerPage - itemIdx <= itemsPerRow;
return ( return (
<tr key={rowIdx}> <div
{row.map(function (item, colIdx) { key={itemIdx}
return ( style={{
<td key={colIdx}> width: `${itemWidth}mm`,
<div height: `${itemHeight}mm`,
style={{ border: borderShowMode === "grid" ? "1px solid #000" : "none",
width: `${itemWidth}mm`, paddingLeft: isFirstColumn ? `${Math.max(printerMargin.left - margin.left, 0)}mm` : 0,
height: `${itemHeight}mm`, paddingRight: isLastColumn ? `${Math.max(printerMargin.right - margin.right, 0)}mm` : 0,
border: borderShowMode === "grid" ? "1px solid #000" : "none", paddingTop: isFirstRow ? `${Math.max(printerMargin.top - margin.top, 0)}mm` : 0,
paddingLeft: colIdx === 0 ? `${Math.max(printerMargin.left - margin.left, 0)}mm` : 0, paddingBottom: isLastRow ? `${Math.max(printerMargin.bottom - margin.bottom, 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, {item}
paddingBottom: </div>
rowIdx === paperRows - 1 ? `${Math.max(printerMargin.bottom - margin.bottom, 0)}mm` : 0,
}}
>
{item}
</div>
</td>
);
})}
</tr>
); );
}); });
@@ -160,7 +145,8 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
<div <div
className="print-page-area" className="print-page-area"
style={{ style={{
border: borderShowMode !== "none" ? "1px solid #000" : "none", outline: borderShowMode !== "none" ? "1px solid #000" : "none",
outlineOffset: "-1px",
height: `${paperHeight - margin.top - margin.bottom}mm`, height: `${paperHeight - margin.top - margin.bottom}mm`,
width: `${paperWidth - margin.left - margin.right}mm`, width: `${paperWidth - margin.left - margin.right}mm`,
marginTop: `calc(${margin.top}mm - ${borderShowMode !== "none" ? "1px" : "0px"})`, marginTop: `calc(${margin.top}mm - ${borderShowMode !== "none" ? "1px" : "0px"})`,
@@ -169,35 +155,27 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
marginBottom: `calc(${margin.bottom}mm - ${borderShowMode !== "none" ? "1px" : "0px"})`, marginBottom: `calc(${margin.bottom}mm - ${borderShowMode !== "none" ? "1px" : "0px"})`,
}} }}
> >
<table <div
style={{ style={{
alignContent: "flex-start", display: "flex",
flexWrap: "wrap",
rowGap: `${spacing.vertical}mm`,
columnGap: `${spacing.horizontal}mm`,
paddingTop: `${spacing.vertical}mm`,
paddingLeft: `${spacing.horizontal}mm`,
}} }}
> >
{itemRows} {itemDivs}
</table> </div>
</div> </div>
</div> </div>
); );
}); });
return ( 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}> <Row gutter={16}>
<Col span={14}> <Col span={14}>
{t("printing.generic.description")}
<div <div
style={{ style={{
transform: "translateZ(0)", transform: "translateZ(0)",
@@ -211,38 +189,44 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
style={{ style={{
transform: `scale(${previewScale})`, transform: `scale(${previewScale})`,
transformOrigin: "top left", transformOrigin: "top left",
display: "block",
}} }}
> >
<style> <style>
{` {`
@media print { @media print {
@page { html, body {
size: auto; height: initial !important;
margin: 0; overflow: initial !important;
} -webkit-print-color-adjust: exact;
.print-container { }
transform: scale(1) !important;
} @page {
.print-page { size: auto;
page-break-before: auto; margin: 0;
} }
.print-container {
transform: scale(1) !important;
}
.print-page {
page-break-before: auto;
}
} }
@media screen { @media screen {
.print-page { .print-page {
margin-top: 10mm; margin-top: 10mm;
} }
} }
.print-page table { .print-page {
border-spacing: ${spacing.horizontal}mm ${spacing.vertical}mm; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
border-collapse: separate; line-height: 1.2;
} }
.print-page td { .print-page * {
padding: 0; box-sizing: border-box;
} }
${style ?? ""} ${style ?? ""}
`} `}
</style> </style>
@@ -367,6 +351,8 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
{extraSettings} {extraSettings}
</Collapse.Panel> </Collapse.Panel>
<Collapse.Panel header={t("printing.generic.layoutSettings")} key="2"> <Collapse.Panel header={t("printing.generic.layoutSettings")} key="2">
{t("printing.generic.description")}
<Divider />
<Form.Item label={t("printing.generic.paperSize")}> <Form.Item label={t("printing.generic.paperSize")}>
<Select <Select
value={paperSize} value={paperSize}
@@ -786,7 +772,16 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
</Form> </Form>
</Col> </Col>
</Row> </Row>
</Modal> <Row justify={"end"}>
<Col>
<ReactToPrint
key="print-button"
trigger={() => <Button type="primary">{t("printing.generic.print")}</Button>}
content={() => printRef.current}
/>
</Col>
</Row>
</>
); );
}; };

View File

@@ -11,28 +11,24 @@ interface QRCodeData {
} }
interface QRCodePrintingDialogProps { interface QRCodePrintingDialogProps {
visible: boolean;
items: QRCodeData[]; items: QRCodeData[];
printSettings: QRCodePrintSettings; printSettings: QRCodePrintSettings;
setPrintSettings: (setPrintSettings: QRCodePrintSettings) => void; setPrintSettings: (setPrintSettings: QRCodePrintSettings) => void;
onCancel: () => void;
extraSettings?: JSX.Element; extraSettings?: JSX.Element;
extraSettingsStart?: JSX.Element; extraSettingsStart?: JSX.Element;
} }
const QRCodePrintingDialog: React.FC<QRCodePrintingDialogProps> = ({ const QRCodePrintingDialog: React.FC<QRCodePrintingDialogProps> = ({
visible,
items, items,
printSettings, printSettings,
setPrintSettings, setPrintSettings,
onCancel,
extraSettings, extraSettings,
extraSettingsStart, extraSettingsStart,
}) => { }) => {
const t = useTranslate(); const t = useTranslate();
const showContent = printSettings?.showContent === undefined ? true : printSettings?.showContent; const showContent = printSettings?.showContent === undefined ? true : printSettings?.showContent;
const textSize = printSettings?.textSize || 5; const textSize = printSettings?.textSize || 3;
const showSpoolmanIcon = printSettings?.showSpoolmanIcon === undefined ? true : printSettings?.showSpoolmanIcon; const showSpoolmanIcon = printSettings?.showSpoolmanIcon === undefined ? true : printSettings?.showSpoolmanIcon;
const elements = items.map((item) => { const elements = items.map((item) => {
@@ -55,8 +51,6 @@ const QRCodePrintingDialog: React.FC<QRCodePrintingDialogProps> = ({
return ( return (
<PrintingDialog <PrintingDialog
visible={visible}
title={t("printing.qrcode.title")}
items={elements} items={elements}
printSettings={printSettings.printSettings} printSettings={printSettings.printSettings}
setPrintSettings={(newSettings) => { setPrintSettings={(newSettings) => {
@@ -123,7 +117,7 @@ const QRCodePrintingDialog: React.FC<QRCodePrintingDialogProps> = ({
.print-page .print-qrcode-item { .print-page .print-qrcode-item {
display: flex; display: flex;
width: 100%; width: 100%;
max-height: 100%; height: 100%;
justify-content: center; justify-content: center;
} }
@@ -135,6 +129,7 @@ const QRCodePrintingDialog: React.FC<QRCodePrintingDialogProps> = ({
.print-page .print-qrcode { .print-page .print-qrcode {
width: auto !important; width: auto !important;
height: auto !important; height: auto !important;
padding: 2mm;
} }
.print-page .print-qrcode-title { .print-page .print-qrcode-title {
@@ -153,7 +148,6 @@ const QRCodePrintingDialog: React.FC<QRCodePrintingDialogProps> = ({
max-width: 100%; max-width: 100%;
} }
`} `}
onCancel={onCancel}
/> />
); );
}; };

View File

@@ -1,6 +1,6 @@
import { Button, Flex, Form, Input, Modal, Popconfirm, Select, Space, Switch, Table, Typography } from "antd"; import { Button, Flex, Form, Input, Modal, Popconfirm, Select, Space, Switch, Table, Typography } from "antd";
import { IFilament } from "../../pages/filaments/model"; import { IFilament } from "../filaments/model";
import { ISpool } from "../../pages/spools/model"; import { ISpool } from "../spools/model";
import QRCodePrintingDialog from "./qrCodePrintingDialog"; import QRCodePrintingDialog from "./qrCodePrintingDialog";
import { useSavedState } from "../../utils/saveload"; import { useSavedState } from "../../utils/saveload";
import { useTranslate } from "@refinedev/core"; import { useTranslate } from "@refinedev/core";
@@ -21,12 +21,10 @@ import { EntityType, useGetFields } from "../../utils/queryFields";
const { Text } = Typography; const { Text } = Typography;
interface SpoolQRCodePrintingDialog { interface SpoolQRCodePrintingDialog {
visible: boolean;
items: ISpool[]; items: ISpool[];
onCancel: () => void;
} }
const SpoolQRCodePrintingDialog: React.FC<SpoolQRCodePrintingDialog> = ({ visible, items, onCancel }) => { const SpoolQRCodePrintingDialog: React.FC<SpoolQRCodePrintingDialog> = ({ items }) => {
const t = useTranslate(); const t = useTranslate();
// Selected setting state // Selected setting state
@@ -210,8 +208,6 @@ Lot Nr: {lot_nr}
return ( return (
<QRCodePrintingDialog <QRCodePrintingDialog
visible={visible}
onCancel={onCancel}
printSettings={selectedPrintSetting.labelSettings} printSettings={selectedPrintSetting.labelSettings}
setPrintSettings={(newSettings) => { setPrintSettings={(newSettings) => {
selectedPrintSetting.labelSettings = newSettings; selectedPrintSetting.labelSettings = newSettings;
@@ -276,6 +272,7 @@ Lot Nr: {lot_nr}
<p <p
style={{ style={{
padding: "1mm 1mm 1mm 0", padding: "1mm 1mm 1mm 0",
margin: 0,
whiteSpace: "pre-wrap", whiteSpace: "pre-wrap",
}} }}
> >

View File

@@ -1,18 +1,16 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Modal, Table, Checkbox, Space, Row, Col, message } from "antd"; import { Modal, Table, Checkbox, Space, Row, Col, message, Button } from "antd";
import { ISpool } from "../pages/spools/model"; import { ISpool } from "../spools/model";
import { FilteredQueryColumn, SortedColumn, SpoolIconColumn } from "./column"; import { FilteredQueryColumn, SortedColumn, SpoolIconColumn } from "../../components/column";
import { TableState } from "../utils/saveload"; import { TableState } from "../../utils/saveload";
import { useTable } from "@refinedev/antd"; import { useTable } from "@refinedev/antd";
import { t } from "i18next"; import { t } from "i18next";
import { useSpoolmanFilamentFilter, useSpoolmanMaterials } from "./otherModels"; import { useSpoolmanFilamentFilter, useSpoolmanMaterials } from "../../components/otherModels";
import { removeUndefined } from "../utils/filtering"; import { removeUndefined } from "../../utils/filtering";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
interface Props { interface Props {
visible: boolean;
description?: string; description?: string;
onCancel: () => void;
onContinue: (selectedSpools: ISpool[]) => void; onContinue: (selectedSpools: ISpool[]) => void;
} }
@@ -37,13 +35,14 @@ function collapseSpool(element: ISpool): ISpoolCollapsed {
}; };
} }
const SpoolSelectModal: React.FC<Props> = ({ visible, description, onCancel, onContinue }) => { const SpoolSelectModal: React.FC<Props> = ({ description, onContinue }) => {
const [selectedItems, setSelectedItems] = useState<number[]>([]); const [selectedItems, setSelectedItems] = useState<number[]>([]);
const [showArchived, setShowArchived] = useState(false); const [showArchived, setShowArchived] = useState(false);
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const navigate = useNavigate(); const navigate = useNavigate();
const { tableProps, sorters, filters, current, pageSize } = useTable<ISpoolCollapsed>({ const { tableProps, sorters, filters, current, pageSize } = useTable<ISpoolCollapsed>({
resource: "spool",
meta: { meta: {
queryParams: { queryParams: {
["allow_archived"]: showArchived, ["allow_archived"]: showArchived,
@@ -116,22 +115,7 @@ const SpoolSelectModal: React.FC<Props> = ({ visible, description, onCancel, onC
}; };
return ( 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} {contextHolder}
<Space direction="vertical" style={{ width: "100%" }}> <Space direction="vertical" style={{ width: "100%" }}>
{description && <div>{description}</div>} {description && <div>{description}</div>}
@@ -210,7 +194,19 @@ const SpoolSelectModal: React.FC<Props> = ({ visible, description, onCancel, onC
</Col> </Col>
</Row> </Row>
</Space> </Space>
</Modal> <Button
onClick={() => {
if (selectedItems.length === 0) {
messageApi.open({
type: "error",
content: t("printing.spoolSelect.noSpoolsSelected"),
});
return;
}
onContinue(dataSource.filter((spool) => selectedItems.includes(spool.id)));
}}
></Button>
</>
); );
}; };

View File

@@ -26,7 +26,6 @@ import {
CustomFieldColumn, CustomFieldColumn,
} from "../../components/column"; } from "../../components/column";
import { setSpoolArchived } from "./functions"; import { setSpoolArchived } from "./functions";
import SelectAndPrint from "../../components/selectAndPrintDialog";
import { import {
useSpoolmanFilamentFilter, useSpoolmanFilamentFilter,
useSpoolmanLocations, useSpoolmanLocations,
@@ -254,7 +253,6 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
<List <List
headerButtons={({ defaultButtons }) => ( headerButtons={({ defaultButtons }) => (
<> <>
<SelectAndPrint />
<Button <Button
type="primary" type="primary"
icon={<InboxOutlined />} icon={<InboxOutlined />}