Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 88 additions & 2 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"i18next": "^25.7.3",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"jszip": "3.10.1",
"react": "^19.2.3",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
Expand All @@ -39,19 +40,19 @@
"@refinedev/cli": "^2.16.50",
"@types/loadable__component": "^5.13.10",
"@types/node": "^25.0.3",
"@types/react-dom": "^19.2.3",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^5.1.2",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.26",
"eslint-plugin-react": "^7.37.5",
"eslint": "^9.39.2",
"globals": "^17.0.0",
"prettier": "3.7.4",
"typescript-eslint": "^8.52.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.52.0",
"vite": "^7.3.0",
"vite-plugin-mkcert": "^1.17.9",
"vite-plugin-pwa": "^1.2.0"
Expand Down
31 changes: 28 additions & 3 deletions client/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,21 @@
"helpMargin": "Margins should be configured to match your label paper and printer, changing these will affect the size of the entire grid.",
"helpPrinterMargin": "Safe-Zone should be set to how close to the paper edge your printer can print, changing these will not affect the entire grid.",
"print": "Print",
"exportLabels": "Export Labels",
"columns": "Columns",
"rows": "Rows",
"paperSize": "Paper Size",
"customSize": "Custom",
"dimensions": "Dimensions",
"amlLabelSize": "AML Label Size",
"exportFormat": "Export Format",
"exportFormatOptions": {
"png": "PNG",
"aml": "AML"
},
"exportAsZip": "Export as .zip",
"exportDpi": "Export DPI",
"exportDpiHelp": "Higher DPI makes sharper PNG/AML exports but increases file size and export time.",
"showBorder": "Show Border",
"previewScale": "Preview Scale",
"skipItems": "Skip Items",
Expand All @@ -92,6 +102,10 @@
"grid": "Grid"
},
"settings": "Presets",
"spoolPrintPresets": "Spool Print Presets",
"filamentPrintPresets": "Filament Print Presets",
"spoolImagePresets": "Spool Image Presets",
"filamentImagePresets": "Filament Image Presets",
"defaultSettings": "Default",
"addSettings": "Add New Preset",
"newSetting": "New",
Expand All @@ -100,14 +114,23 @@
"deleteSettingsConfirm": "Are you sure you want to delete this preset?",
"settingsName": "Preset Name",
"saveSetting": "Save Presets",
"saveAsImage": "Save as Image"
"saveAsImage": "Save as Image",
"saveAsAmlLabels": "Save as AML (Labels)"
},
"qrcode": {
"button": "Print Labels",
"exportButton": "Export Labels",
"exportFilamentTitle": "Export Filament Labels",
"exportSpoolTitle": "Export Spool Labels",
"printFilamentTitle": "Print Filament Labels",
"printSpoolTitle": "Print Spool Labels",
"selectButton": "Export/Print",
"selectTitle": "Export / Print Labels",
"title": "Label Printing",
"template": "Label Template",
"filenameTemplate": "Filename Template",
"filenameTemplateTooltipSpool": "Use {} to insert values of the spool object as text. Refer to the label template rules and available tags for more details.",
"filenameTemplateTooltipFilament": "Use {} to insert values of the filament object as text. Refer to the label template rules and available tags for more details.",
"templateHelp": "Use {} to insert values of the spool object as text. For example, {id} will be replaced with the spool id, or {filament.material} will be replaced with the material of the spool. if a value is missing it will be replaced with \"?\". A second set of {} can be used to remove this. In addition, any text between the sets of {} will be removed if the value is missing. For example, {Lot Nr: {lot_nr}} will only show the label if the spool has a lot number. Enclose text with double asterix ** to make it bold. Click the button to view a list of all available tags.",
"templateHelpFilament": "Use {} to insert values of the filament object as text. For example, {id} will be replaced with the filament id, or {vendor.name} will be replaced with the vendor name. If a value is missing it will be replaced with \"?\". A second set of {} can be used to remove this. In addition, any text between the sets of {} will be removed if the value is missing. For example, {Article: {article_number}} will only show the label if a filament has an article number. Enclose text with double asterix ** to make it bold. Click the button to view a list of all available tags.",
"textSize": "Label Text Size",
Expand All @@ -130,7 +153,8 @@
},
"spoolSelect": {
"title": "Select Spools",
"description": "Select spools to print labels for.",
"description": "Select spools to export or print labels for.",
"searchPlaceholder": "Search vendor, filament, material, location, lot #",
"showArchived": "Show Archived",
"noSpoolsSelected": "You have not selected any spools.",
"selectAll": "Select/Unselect All",
Expand All @@ -139,7 +163,8 @@
},
"filamentSelect": {
"title": "Select Filaments",
"description": "Select filaments to print labels for.",
"description": "Select filaments to export or print labels for.",
"searchPlaceholder": "Search vendor, name, material, article #",
"noFilamentsSelected": "You have not selected any filaments.",
"selectAll": "Select/Unselect All",
"selectedTotal_one": "{{count}} filament selected",
Expand Down
4 changes: 4 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ function App() {
/>
<Route path="edit/:id" element={<LoadableResourcePage resource="spools" page="edit" />} />
<Route path="show/:id" element={<LoadableResourcePage resource="spools" page="show" />} />
<Route path="labels" element={<LoadablePage name="spoolLabels" />} />
<Route path="print" element={<LoadablePage name="printing" />} />
<Route path="export" element={<LoadablePage name="printingExport" />} />
</Route>
<Route path="/filament">
<Route index element={<LoadableResourcePage resource="filaments" page="list" />} />
Expand All @@ -208,7 +210,9 @@ function App() {
/>
<Route path="edit/:id" element={<LoadableResourcePage resource="filaments" page="edit" />} />
<Route path="show/:id" element={<LoadableResourcePage resource="filaments" page="show" />} />
<Route path="labels" element={<LoadablePage name="filamentLabels" />} />
<Route path="print" element={<LoadablePage name="filamentPrinting" />} />
<Route path="export" element={<LoadablePage name="filamentExport" />} />
</Route>
<Route path="/vendor">
<Route index element={<LoadableResourcePage resource="vendors" page="list" />} />
Expand Down
86 changes: 30 additions & 56 deletions client/src/components/otherModels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ import { IFilament } from "../pages/filaments/model";
import { IVendor } from "../pages/vendors/model";
import { getAPIURL } from "../utils/url";

/**
* Factory function to create a reusable query hook for fetching and sorting string arrays from API endpoints.
* @param queryKey - Unique cache key for react-query
* @param endpoint - API endpoint to fetch from
* @param enabled - Whether the query should be enabled
*/
function useSimpleSortedArrayQuery<T>(queryKey: string[], endpoint: string, enabled: boolean = false) {
return useQuery<T[], unknown, T[]>({
enabled,
queryKey,
queryFn: async () => {
const response = await fetch(getAPIURL() + endpoint);
if (!response.ok) {
throw new Error(`Failed to fetch from ${endpoint}: ${response.statusText}`);
}
return response.json();
},
select: (data) => {
if (Array.isArray(data)) {
return [...data].sort();
}
return [];
},
});
}

export function useSpoolmanFilamentFilter(enabled: boolean = false) {
return useQuery<IFilament[], unknown, ColumnFilterItem[]>({
enabled: enabled,
Expand Down Expand Up @@ -135,69 +161,17 @@ export function useSpoolmanVendors(enabled: boolean = false) {
}

export function useSpoolmanMaterials(enabled: boolean = false) {
return useQuery<string[]>({
enabled: enabled,
queryKey: ["materials"],
queryFn: async () => {
const response = await fetch(getAPIURL() + "/material");
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
},
select: (data) => {
return data.sort();
},
});
return useSimpleSortedArrayQuery<string>(["materials"], "/material", enabled);
}

export function useSpoolmanArticleNumbers(enabled: boolean = false) {
return useQuery<string[]>({
enabled: enabled,
queryKey: ["articleNumbers"],
queryFn: async () => {
const response = await fetch(getAPIURL() + "/article-number");
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
},
select: (data) => {
return data.sort();
},
});
return useSimpleSortedArrayQuery<string>(["articleNumbers"], "/article-number", enabled);
}

export function useSpoolmanLotNumbers(enabled: boolean = false) {
return useQuery<string[]>({
enabled: enabled,
queryKey: ["lotNumbers"],
queryFn: async () => {
const response = await fetch(getAPIURL() + "/lot-number");
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
},
select: (data) => {
return data.sort();
},
});
return useSimpleSortedArrayQuery<string>(["lotNumbers"], "/lot-number", enabled);
}

export function useSpoolmanLocations(enabled: boolean = false) {
return useQuery<string[]>({
enabled: enabled,
queryKey: ["locations"],
queryFn: async () => {
const response = await fetch(getAPIURL() + "/location");
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
},
select: (data) => {
return data.sort();
},
});
return useSimpleSortedArrayQuery<string>(["locations"], "/location", enabled);
}
Loading