Skip to content

Commit

Permalink
Converted chapter actions into drawer
Browse files Browse the repository at this point in the history
  • Loading branch information
umerfaruk committed Jul 13, 2024
1 parent 66ffe31 commit a56eea3
Show file tree
Hide file tree
Showing 14 changed files with 448 additions and 210 deletions.
187 changes: 187 additions & 0 deletions src/components/batchActionDrawer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import React, { useCallback, useState } from "react";
import { useEffect } from "react";

// 3rd party imports
const { App, Drawer, Tooltip, Button, Space, List, Avatar } = require("antd")

// Local imports
import { FaFileUpload, FaHourglass, FaRegCheckCircle, FaRegTimesCircle, LoadingOutlined } from '/src/icons';
import { ProcessStatus } from "/src/models";

// -------------------------------------------
const getIcon = (request) => {
switch (request.status) {
case ProcessStatus.Pending:
return <FaHourglass />;
case ProcessStatus.InProcess:
return <LoadingOutlined />;
case ProcessStatus.CreatingBook:
case ProcessStatus.UploadingContents:
return <FaFileUpload />
case ProcessStatus.Completed:
return <FaRegCheckCircle />;
case ProcessStatus.Failed:
return <FaRegTimesCircle style={{ color: 'red' }} />;
default:
return null;
}
}
// -------------------------------------------

const RequestList = ({ title, requests, itemTitle, itemDescription }) => {
return (<>
{title}
<List dataSource={requests}
renderItem={request => (
<List.Item key={request.id}>
<List.Item.Meta
avatar={<Avatar size={32} icon={getIcon(request)} />}
title={itemTitle ? itemTitle(request.data) : request.id}
description={itemDescription && itemDescription(request.data)} />
</List.Item>)} />
</>);
}
// -------------------------------------------

const BatchActionDrawer = ({
title,
icon,
tooltip,
buttonType,
disabled,
sliderTitle,
placement = 'left',
// Called when ok button pressed. If returns false or null, it will do nothing.
// Return payload for requests and it will call mutation with it
// if payload is a function, this function will be called for each call to api and response from this function
// will be sent as payload. Request data would be passed to the function
// Do any validation on this callback if needed
onOk = () => { },
onShow = () => { },
onClose = () => { },
errorMessage,
successMessage,
closable,
listTitle,
items,
itemTitle,
itemDescription,
// Mutation to be called. Mutation must take parameter like following:
// { requests, payload, onProgress }
// - requests : this is the list of request objects to make,
// structure of request is { status: ProcessStatus.Pending,id: 1, data: { text : 'some text' } }
// - payload : this is the payload to be sent as part of request
// if this is a callback it will be called with the request data and must return the payload for request for that particular object else payload object is sent.
// - onProgress : callback called for each item when its progress status for request is changed. Parameter is request object with updated status
mutation,
t,
children
}) => {
const { message } = App.useApp();
const [open, setOpen] = useState(false);
const [requests, setRequests] = useState([]);

const onShowDrawer = () => {
setOpen(true);
onShow();
}
const onCloseDrawer = () => {
setOpen(false);
onClose();
}

const onProgress = useCallback((request) => {
let newRequests = [...requests];
let index = newRequests.findIndex(x => x.id === request.id);
if (index === -1) return;
if (request.status) {
newRequests[index].status = request.status;
}

setRequests(newRequests);
}, [requests]);

const onSubmit = async () => {
var payload = await onOk();

if (!payload) return;

if (mutation) {
await mutation({ requests, payload, onProgress })
}

const hasFailure = requests.find(x => x.status == ProcessStatus.Failed);
if (hasFailure) {
if (errorMessage) {
message.error(errorMessage);
}
} else {
if (successMessage) {
message.success(successMessage);
}

onCloseDrawer();
}
};

useEffect(() => {
if (open) return;

if (items) {
let mappedRequests = items.map((item, index) => ({
status: ProcessStatus.Pending,
id: index,
data: item
}));

setRequests(mappedRequests);
}
}, [open, items]);

return (
<>
<Tooltip title={tooltip}>
<Button
type={buttonType}
onClick={onShowDrawer}
disabled={disabled}
icon={icon}
>
{title}
</Button>
</Tooltip>
<Drawer
title={sliderTitle}
placement={placement}
onClose={onCloseDrawer}
open={open}
closable={closable}
maskClosable={closable}
extra={<Space>
<Button
onClick={onCloseDrawer}
disabled={!closable}>
{t('actions.cancel')}
</Button>
<Button type="primary" onClick={onSubmit} disabled={!closable}>
{t('actions.ok')}
</Button>
</Space>}>
<Space.Compact direction="vertical"
style={{
width: '100%',
}}
>
{children}
<RequestList
title={listTitle}
requests={requests}
itemTitle={itemTitle}
itemDescription={itemDescription} />
</Space.Compact>
</Drawer>
</>);
};


export default BatchActionDrawer;
111 changes: 44 additions & 67 deletions src/components/books/chapters/chapterAssignButton.jsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,60 @@
import React, { useState } from "react";
import React from "react";

// Third party libraries
import { App, Button, Modal, Form, Space, Tooltip, Tag } from "antd";
import { Form, Space, Typography } from "antd";

// Local imports
import { EditOutlined, FileDoneOutlined, FaUserAlt } from "/src/icons";
import { useAssignChapterMutation } from "/src/store/slices/booksSlice";
import { useAssignChaptersMutation } from "/src/store/slices/booksSlice";
import UserSelect from "/src/components/userSelect";
import BatchActionDrawer from "/src/components/batchActionDrawer";

// ------------------------------------------------------

const ChapterAssignButton = ({ libraryId, chapters, t, type, showDetails = true }) => {
const { message } = App.useApp();
const ChapterAssignButton = ({ libraryId, chapters, t, type, showDetails = true, showIcon = true }) => {
const [form] = Form.useForm();
const [open, setOpen] = useState(false);
const [assignChapter, { isLoading: isAdding }] = useAssignChapterMutation();
const [assignChapters, { isLoading: isAdding }] = useAssignChaptersMutation();
const count = chapters ? chapters.length : 0;

let assignment = [];

if (chapters.length === 1) {
if (chapters[0].reviewerAccountId) {
assignment.push(
<Tag icon={<FileDoneOutlined />} closable={false}>
{chapters[0].reviewerAccountName}
</Tag>
<Space.Compact>
<FileDoneOutlined />
<Typography>{chapters[0].reviewerAccountName}</Typography>
</Space.Compact>
);
}
if (chapters[0].writerAccountId) {
assignment.push(
<Tag icon={<EditOutlined />} closable={false}>
{chapters[0].writerAccountName}
</Tag>
<Space.Compact>
<EditOutlined />
<Typography>{chapters[0].writerAccountName}</Typography>
</Space.Compact>
);
}
}

const onSubmit = (values) => {
const payload = values.id === "none" ? {
unassign: true
} :
{
accountId: values.id === "me" ? null : values.id,
};

const promises = chapters
.map((chapter) => {
if (chapter && chapter.links && chapter.links.assign) {
return assignChapter({ chapter, payload }).unwrap();
}
return Promise.resolve();
});
const onOk = async () => {
try {

Promise.all(promises)
.then(() => message.success(t("chapter.actions.assign.success", { count }))
)
.catch(() => message.error(t("chapter.actions.assign.error", { count }))
);
let values = await form.validateFields();
return values.id === "none" ? {
unassign: true
} :
{
accountId: values.id === "me" ? null : values.id,
};
}
catch {
return false;
}
};

const onOk = () => form
.validateFields()
.then((values) => {
onSubmit(values);
})
.catch((info) => {
console.error(info);
});

const onShow = () => {
form.resetFields();
setOpen(true);
};

let data = {
Expand All @@ -79,32 +64,24 @@ const ChapterAssignButton = ({ libraryId, chapters, t, type, showDetails = true

return (
<>
<Tooltip title={t('chapter.actions.assign.label')}>
<Button
type={type}
onClick={onShow}
disabled={count === 0}
>
{showDetails && assignment.length > 0 ? assignment : <FaUserAlt />}
</Button>
</Tooltip>
<Modal
open={open}
title={t("chapter.actions.assign.title", { count })}
<BatchActionDrawer t={t}
tooltip={t('chapter.actions.assign.label')}
buttonType={type}
disabled={count === 0}
title={showDetails && assignment.length > 0 ? assignment : null}
icon={showIcon && <FaUserAlt />}
sliderTitle={t("chapter.actions.assign.label")}
onOk={onOk}
onCancel={() => setOpen(false)}
closable={false}
okButtonProps={{ disabled: isAdding }}
cancelButtonProps={{ disabled: isAdding }}
closable={!isAdding}
onShow={onShow}
listTitle={t("chapters.title")}
items={chapters}
itemTitle={chapter => chapter.title}
mutation={assignChapters}
successMessage={t("chapter.actions.assign.success", { count })}
errorMessage={t("chapter.actions.assign.error", { count })}
>
<Form form={form} layout="vertical" initialValues={data}>
<Space>
{t("chapter.actions.assign.message", {
chapterNumber: chapters
? chapters.map((p) => p.title).join(",")
: 0,
})}
</Space>
<Form.Item
name="id"
label={t("chapter.user.label")}
Expand All @@ -123,7 +100,7 @@ const ChapterAssignButton = ({ libraryId, chapters, t, type, showDetails = true
addMeOption />
</Form.Item>
</Form>
</Modal>
</BatchActionDrawer >
</>
);
};
Expand Down
Loading

0 comments on commit a56eea3

Please sign in to comment.