Client: Support spool archiving in frontend
This commit is contained in:
@@ -21,6 +21,10 @@
|
|||||||
"undo": "Undo",
|
"undo": "Undo",
|
||||||
"import": "Import",
|
"import": "Import",
|
||||||
"clone": "Clone",
|
"clone": "Clone",
|
||||||
|
"archive": "Archive",
|
||||||
|
"unArchive": "Unarchive",
|
||||||
|
"hideArchived": "Hide Archived",
|
||||||
|
"showArchived": "Show Archived",
|
||||||
"notAccessTitle": "You don't have permission to access",
|
"notAccessTitle": "You don't have permission to access",
|
||||||
"hideColumns": "Hide Columns",
|
"hideColumns": "Hide Columns",
|
||||||
"clearFilters": "Clear Filters"
|
"clearFilters": "Clear Filters"
|
||||||
@@ -41,6 +45,8 @@
|
|||||||
"loading": "Loading",
|
"loading": "Loading",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
"tags": {
|
"tags": {
|
||||||
"clone": "Clone"
|
"clone": "Clone"
|
||||||
},
|
},
|
||||||
@@ -63,7 +69,8 @@
|
|||||||
"first_used": "First Used",
|
"first_used": "First Used",
|
||||||
"last_used": "Last Used",
|
"last_used": "Last Used",
|
||||||
"registered": "Registered",
|
"registered": "Registered",
|
||||||
"comment": "Comment"
|
"comment": "Comment",
|
||||||
|
"archived": "Archived"
|
||||||
},
|
},
|
||||||
"fields_help": {
|
"fields_help": {
|
||||||
"used_weight": "How much filament has been used from the spool. A new spool should have 0g used.",
|
"used_weight": "How much filament has been used from the spool. A new spool should have 0g used.",
|
||||||
@@ -75,7 +82,11 @@
|
|||||||
"clone": "Clone Spool",
|
"clone": "Clone Spool",
|
||||||
"edit": "Edit Spool",
|
"edit": "Edit Spool",
|
||||||
"list": "Spools",
|
"list": "Spools",
|
||||||
"show": "Show Spool"
|
"show": "Show Spool",
|
||||||
|
"archive": "Archive Spool"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"archive": "Are you sure you want to archive this spool?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filament": {
|
"filament": {
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
"undo": "Ångra",
|
"undo": "Ångra",
|
||||||
"import": "Importera",
|
"import": "Importera",
|
||||||
"clone": "Klona",
|
"clone": "Klona",
|
||||||
|
"archive": "Arkivera",
|
||||||
|
"unArchive": "Avarkivera",
|
||||||
|
"hideArchived": "Dölj arkiverade",
|
||||||
|
"showArchived": "Visa arkiverade",
|
||||||
"notAccessTitle": "Du har inte tillgång till denna sida",
|
"notAccessTitle": "Du har inte tillgång till denna sida",
|
||||||
"hideColumns": "Dölj kolumner",
|
"hideColumns": "Dölj kolumner",
|
||||||
"clearFilters": "Rensa filter"
|
"clearFilters": "Rensa filter"
|
||||||
@@ -41,6 +45,8 @@
|
|||||||
"loading": "Laddar...",
|
"loading": "Laddar...",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"unknown": "Okänd",
|
"unknown": "Okänd",
|
||||||
|
"yes": "Ja",
|
||||||
|
"no": "Nej",
|
||||||
"tags": {
|
"tags": {
|
||||||
"clone": "Klona"
|
"clone": "Klona"
|
||||||
},
|
},
|
||||||
@@ -63,7 +69,8 @@
|
|||||||
"first_used": "Användes först",
|
"first_used": "Användes först",
|
||||||
"last_used": "Användes senast",
|
"last_used": "Användes senast",
|
||||||
"registered": "Registrerad",
|
"registered": "Registrerad",
|
||||||
"comment": "Kommentar"
|
"comment": "Kommentar",
|
||||||
|
"archived": "Arkiverad"
|
||||||
},
|
},
|
||||||
"fields_help": {
|
"fields_help": {
|
||||||
"used_weight": "Hur mycket filament som använts på spolen. En ny spole har värdet 0g.",
|
"used_weight": "Hur mycket filament som använts på spolen. En ny spole har värdet 0g.",
|
||||||
@@ -75,7 +82,11 @@
|
|||||||
"clone": "Klona spole",
|
"clone": "Klona spole",
|
||||||
"edit": "Ändra spole",
|
"edit": "Ändra spole",
|
||||||
"list": "Spolar",
|
"list": "Spolar",
|
||||||
"show": "Visa spole"
|
"show": "Visa spole",
|
||||||
|
"archive": "Arkivera spole"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"archive": "Är du säker på att du vill arkivera denna spole?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filament": {
|
"filament": {
|
||||||
|
|||||||
17
client/src/pages/spools/functions.ts
Normal file
17
client/src/pages/spools/functions.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { ISpool } from "./model";
|
||||||
|
|
||||||
|
|
||||||
|
export async function setSpoolArchived(spool: ISpool, archived: boolean) {
|
||||||
|
const apiEndpoint = import.meta.env.VITE_APIURL;
|
||||||
|
const init: RequestInit = {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
archived: archived,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const request = new Request(apiEndpoint + "/spool/" + spool.id);
|
||||||
|
await fetch(request, init);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
IResourceComponentsProps,
|
IResourceComponentsProps,
|
||||||
BaseRecord,
|
useInvalidate,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "@refinedev/core";
|
} from "@refinedev/core";
|
||||||
import {
|
import {
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
ShowButton,
|
ShowButton,
|
||||||
CloneButton,
|
CloneButton,
|
||||||
} from "@refinedev/antd";
|
} from "@refinedev/antd";
|
||||||
import { Table, Space, Button, Dropdown } from "antd";
|
import { Table, Space, Button, Dropdown, Modal } from "antd";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import { genericSorter, typeSorters } from "../../utils/sorting";
|
import { genericSorter, typeSorters } from "../../utils/sorting";
|
||||||
@@ -20,9 +20,15 @@ import { ISpool } from "./model";
|
|||||||
import {
|
import {
|
||||||
TableState,
|
TableState,
|
||||||
useInitialTableState,
|
useInitialTableState,
|
||||||
|
useShowArchive,
|
||||||
useStoreInitialState,
|
useStoreInitialState,
|
||||||
} from "../../utils/saveload";
|
} from "../../utils/saveload";
|
||||||
import { EditOutlined, FilterOutlined } from "@ant-design/icons";
|
import {
|
||||||
|
EditOutlined,
|
||||||
|
FilterOutlined,
|
||||||
|
InboxOutlined,
|
||||||
|
ToTopOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
import {
|
import {
|
||||||
DateColumn,
|
DateColumn,
|
||||||
FilteredColumn,
|
FilteredColumn,
|
||||||
@@ -30,9 +36,12 @@ import {
|
|||||||
SortedColumn,
|
SortedColumn,
|
||||||
SpoolIconColumn,
|
SpoolIconColumn,
|
||||||
} from "../../components/column";
|
} from "../../components/column";
|
||||||
|
import { setSpoolArchived } from "./functions";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
|
const { confirm } = Modal;
|
||||||
|
|
||||||
interface ISpoolCollapsed extends ISpool {
|
interface ISpoolCollapsed extends ISpool {
|
||||||
filament_name: string;
|
filament_name: string;
|
||||||
material?: string;
|
material?: string;
|
||||||
@@ -40,10 +49,14 @@ interface ISpoolCollapsed extends ISpool {
|
|||||||
|
|
||||||
export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
|
const invalidate = useInvalidate();
|
||||||
|
|
||||||
// Load initial state
|
// Load initial state
|
||||||
const initialState = useInitialTableState("spoolList");
|
const initialState = useInitialTableState("spoolList");
|
||||||
|
|
||||||
|
// State for the switch to show archived spools
|
||||||
|
const [showArchived, setShowArchived] = useShowArchive("spoolList");
|
||||||
|
|
||||||
// Fetch data from the API
|
// Fetch data from the API
|
||||||
const {
|
const {
|
||||||
tableProps,
|
tableProps,
|
||||||
@@ -56,6 +69,11 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
setCurrent,
|
setCurrent,
|
||||||
setPageSize,
|
setPageSize,
|
||||||
} = useTable<ISpool>({
|
} = useTable<ISpool>({
|
||||||
|
meta: {
|
||||||
|
queryParams: {
|
||||||
|
["allow_archived"]: showArchived,
|
||||||
|
},
|
||||||
|
},
|
||||||
syncWithLocation: false,
|
syncWithLocation: false,
|
||||||
pagination: {
|
pagination: {
|
||||||
mode: "off", // Perform pagination in antd's Table instead. Otherwise client-side sorting/filtering doesn't work.
|
mode: "off", // Perform pagination in antd's Table instead. Otherwise client-side sorting/filtering doesn't work.
|
||||||
@@ -138,10 +156,49 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
return filtered;
|
return filtered;
|
||||||
}, [dataSource, typedFilters, typedSorters]);
|
}, [dataSource, typedFilters, typedSorters]);
|
||||||
|
|
||||||
|
// Function for opening an ant design modal that asks for confirmation for archiving a spool
|
||||||
|
const archiveSpool = async (spool: ISpoolCollapsed, archive: boolean) => {
|
||||||
|
await setSpoolArchived(spool, archive);
|
||||||
|
invalidate({
|
||||||
|
resource: "spool",
|
||||||
|
id: spool.id,
|
||||||
|
invalidates: ["list", "detail"],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const archiveSpoolPopup = async (spool: ISpoolCollapsed) => {
|
||||||
|
// If the spool has no remaining weight, archive it immediately since it's likely not a mistake
|
||||||
|
if (spool.remaining_weight && spool.remaining_weight == 0) {
|
||||||
|
await archiveSpool(spool, true);
|
||||||
|
} else {
|
||||||
|
confirm({
|
||||||
|
title: t("spool.titles.archive"),
|
||||||
|
content: t("spool.messages.archive"),
|
||||||
|
okText: t("buttons.archive"),
|
||||||
|
okType: "primary",
|
||||||
|
cancelText: t("buttons.cancel"),
|
||||||
|
onOk() {
|
||||||
|
return archiveSpool(spool, true);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
headerButtons={({ defaultButtons }) => (
|
headerButtons={({ defaultButtons }) => (
|
||||||
<>
|
<>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<InboxOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setShowArchived(!showArchived);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showArchived
|
||||||
|
? t("buttons.hideArchived")
|
||||||
|
: t("buttons.showArchived")}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<FilterOutlined />}
|
icon={<FilterOutlined />}
|
||||||
@@ -192,6 +249,19 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
// Make archived rows greyed out
|
||||||
|
onRow={(record) => {
|
||||||
|
if (record.archived) {
|
||||||
|
return {
|
||||||
|
style: {
|
||||||
|
fontStyle: "italic",
|
||||||
|
color: "#999",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{SortedColumn({
|
{SortedColumn({
|
||||||
id: "id",
|
id: "id",
|
||||||
@@ -284,7 +354,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
})}
|
})}
|
||||||
<Table.Column
|
<Table.Column
|
||||||
title={t("table.actions")}
|
title={t("table.actions")}
|
||||||
render={(_, record: BaseRecord) => (
|
render={(_, record: ISpoolCollapsed) => (
|
||||||
<Space>
|
<Space>
|
||||||
<EditButton
|
<EditButton
|
||||||
hideText
|
hideText
|
||||||
@@ -304,6 +374,21 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
|||||||
size="small"
|
size="small"
|
||||||
recordItemId={record.id}
|
recordItemId={record.id}
|
||||||
/>
|
/>
|
||||||
|
{record.archived ? (
|
||||||
|
<Button
|
||||||
|
icon={<ToTopOutlined />}
|
||||||
|
title={t("buttons.unArchive")}
|
||||||
|
size="small"
|
||||||
|
onClick={() => archiveSpool(record, false)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
icon={<InboxOutlined />}
|
||||||
|
title={t("buttons.archive")}
|
||||||
|
size="small"
|
||||||
|
onClick={() => archiveSpoolPopup(record)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ export interface ISpool {
|
|||||||
location?: string;
|
location?: string;
|
||||||
lot_nr?: string;
|
lot_nr?: string;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
|
archived: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
|
|||||||
<TextField value={record?.lot_nr} />
|
<TextField value={record?.lot_nr} />
|
||||||
<Title level={5}>{t("spool.fields.comment")}</Title>
|
<Title level={5}>{t("spool.fields.comment")}</Title>
|
||||||
<TextField value={record?.comment} />
|
<TextField value={record?.comment} />
|
||||||
|
<Title level={5}>{t("spool.fields.archived")}</Title>
|
||||||
|
<TextField value={record?.archived ? t("yes") : t("no")} />
|
||||||
</Show>
|
</Show>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,3 +59,18 @@ export function useStoreInitialState(tableId: string, state: TableState) {
|
|||||||
}
|
}
|
||||||
}, [tableId, state.showColumns]);
|
}, [tableId, state.showColumns]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useShowArchive(tableId: string) {
|
||||||
|
const [showArchive, setShowArchive] = React.useState(() => {
|
||||||
|
const savedShowArchive = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-showArchive`) : null;
|
||||||
|
return savedShowArchive ? JSON.parse(savedShowArchive) : false;
|
||||||
|
});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isLocalStorageAvailable) {
|
||||||
|
localStorage.setItem(`${tableId}-showArchive`, JSON.stringify(showArchive));
|
||||||
|
}
|
||||||
|
}, [tableId, showArchive]);
|
||||||
|
|
||||||
|
return [showArchive, setShowArchive] as const;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user