summaryrefslogtreecommitdiffhomepage
path: root/frontend/src/components/MassEditor.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/MassEditor.tsx')
-rw-r--r--frontend/src/components/MassEditor.tsx121
1 files changed, 121 insertions, 0 deletions
diff --git a/frontend/src/components/MassEditor.tsx b/frontend/src/components/MassEditor.tsx
new file mode 100644
index 000000000..9a6fdb0e3
--- /dev/null
+++ b/frontend/src/components/MassEditor.tsx
@@ -0,0 +1,121 @@
+import { useIsAnyMutationRunning, useLanguageProfiles } from "@/apis/hooks";
+import { GetItemId } from "@/utilities";
+import { faCheck, faUndo } from "@fortawesome/free-solid-svg-icons";
+import { uniqBy } from "lodash";
+import { useCallback, useMemo, useState } from "react";
+import { Container, Dropdown, Row } from "react-bootstrap";
+import { UseMutationResult } from "react-query";
+import { useNavigate } from "react-router-dom";
+import { Column, useRowSelect } from "react-table";
+import { ContentHeader, SimpleTable } from ".";
+import { useCustomSelection } from "./tables/plugins";
+
+interface MassEditorProps<T extends Item.Base = Item.Base> {
+ columns: Column<T>[];
+ data: T[];
+ mutation: UseMutationResult<void, unknown, FormType.ModifyItem>;
+}
+
+function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
+ const { columns, data: raw, mutation } = props;
+
+ const [selections, setSelections] = useState<T[]>([]);
+ const [dirties, setDirties] = useState<T[]>([]);
+ const hasTask = useIsAnyMutationRunning();
+ const { data: profiles } = useLanguageProfiles();
+
+ const navigate = useNavigate();
+
+ const onEnded = useCallback(() => navigate(".."), [navigate]);
+
+ const data = useMemo(
+ () => uniqBy([...dirties, ...(raw ?? [])], GetItemId),
+ [dirties, raw]
+ );
+
+ const profileOptions = useMemo(() => {
+ const items: JSX.Element[] = [];
+ if (profiles) {
+ items.push(
+ <Dropdown.Item key="clear-profile">Clear Profile</Dropdown.Item>
+ );
+ items.push(<Dropdown.Divider key="dropdown-divider"></Dropdown.Divider>);
+ items.push(
+ ...profiles.map((v) => (
+ <Dropdown.Item key={v.profileId} eventKey={v.profileId.toString()}>
+ {v.name}
+ </Dropdown.Item>
+ ))
+ );
+ }
+
+ return items;
+ }, [profiles]);
+
+ const { mutateAsync } = mutation;
+
+ const save = useCallback(() => {
+ const form: FormType.ModifyItem = {
+ id: [],
+ profileid: [],
+ };
+ dirties.forEach((v) => {
+ const id = GetItemId(v);
+ if (id) {
+ form.id.push(id);
+ form.profileid.push(v.profileId);
+ }
+ });
+ return mutateAsync(form);
+ }, [dirties, mutateAsync]);
+
+ const setProfiles = useCallback(
+ (key: Nullable<string>) => {
+ const id = key ? parseInt(key) : null;
+
+ const newItems = selections.map((v) => ({ ...v, profileId: id }));
+
+ setDirties((dirty) => {
+ return uniqBy([...newItems, ...dirty], GetItemId);
+ });
+ },
+ [selections]
+ );
+ return (
+ <Container fluid>
+ <ContentHeader scroll={false}>
+ <ContentHeader.Group pos="start">
+ <Dropdown onSelect={setProfiles}>
+ <Dropdown.Toggle disabled={selections.length === 0} variant="light">
+ Change Profile
+ </Dropdown.Toggle>
+ <Dropdown.Menu>{profileOptions}</Dropdown.Menu>
+ </Dropdown>
+ </ContentHeader.Group>
+ <ContentHeader.Group pos="end">
+ <ContentHeader.Button icon={faUndo} onClick={onEnded}>
+ Cancel
+ </ContentHeader.Button>
+ <ContentHeader.AsyncButton
+ icon={faCheck}
+ disabled={dirties.length === 0 || hasTask}
+ promise={save}
+ onSuccess={onEnded}
+ >
+ Save
+ </ContentHeader.AsyncButton>
+ </ContentHeader.Group>
+ </ContentHeader>
+ <Row>
+ <SimpleTable
+ columns={columns}
+ data={data}
+ onSelect={setSelections}
+ plugins={[useRowSelect, useCustomSelection]}
+ ></SimpleTable>
+ </Row>
+ </Container>
+ );
+}
+
+export default MassEditor;