Client: Support spool archiving in frontend

This commit is contained in:
Donkie
2023-07-14 14:21:47 +02:00
parent f89cf8ecd4
commit 7971cda7b5
7 changed files with 150 additions and 8 deletions

View File

@@ -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": {

View File

@@ -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": {

View 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);
}

View File

@@ -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>
)} )}
/> />

View File

@@ -13,4 +13,5 @@ export interface ISpool {
location?: string; location?: string;
lot_nr?: string; lot_nr?: string;
comment?: string; comment?: string;
archived: boolean;
} }

View File

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

View File

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