Skip to content
Merged
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
4 changes: 2 additions & 2 deletions backend/api/v1/v1_data/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from api.v1.v1_users.models import SystemUser
from api.v1.v1_profile.constants import UserRoleTypes
from rtmis.settings import REST_FRAMEWORK
from utils.custom_permissions import IsAdmin, IsApprover
from utils.custom_permissions import IsAdmin, IsApprover, IsSubmitter
from utils.custom_serializer_fields import validate_serializers_message
from utils.export_form import generate_excel

Expand Down Expand Up @@ -822,7 +822,7 @@ def list_pending_batch(request, version):
tags=['Pending Data'],
summary='To get list of pending data by batch')
@api_view(['GET'])
@permission_classes([IsAuthenticated, IsAdmin | IsApprover])
@permission_classes([IsAuthenticated, IsAdmin | IsApprover | IsSubmitter])
def list_pending_data_batch(request, version, batch_id):
batch = get_object_or_404(PendingDataBatch, pk=batch_id)
return Response(ListPendingFormDataSerializer(
Expand Down
7 changes: 7 additions & 0 deletions backend/utils/custom_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
from api.v1.v1_profile.constants import UserRoleTypes


class IsSubmitter(BasePermission):
def has_permission(self, request, view):
if request.user.user_access.role == UserRoleTypes.user:
return True
return False


class IsApprover(BasePermission):
def has_permission(self, request, view):
if request.user.user_access.role == UserRoleTypes.approver:
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Privacy,
Reports,
Report,
DataUploads,
} from "./pages";
import { useCookies } from "react-cookie";
import { store, api, config } from "./lib";
Expand Down Expand Up @@ -97,6 +98,10 @@ const RouteList = () => {
path="/approvals"
element={<Private element={Approvals} alias="approvals" />}
/>
<Route
path="/data/uploads"
element={<Private element={DataUploads} alias="data" />}
/>
<Route
path="/approvers/tree"
element={<Private element={ApproversTree} alias="approvers" />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const ApprovalDetail = ({
const formData = [];
data.data.map((rd) => {
rd.question.map((rq) => {
if (rq.newValue && rq.newValue !== rq.value) {
if (rq.newValue && !isEqual(rq.value, rq.newValue)) {
formData.push({ question: rq.id, value: rq.newValue });
}
});
Expand Down Expand Up @@ -338,7 +338,8 @@ const ApprovalDetail = ({
return (
!!flatten(
rawValues.find((d) => d.id === id)?.data?.map((g) => g.question)
)?.filter((d) => d.newValue && d.newValue !== d.value)?.length || false
)?.filter((d) => d.newValue && !isEqual(d.value, d.newValue))?.length ||
false
);
};

Expand Down Expand Up @@ -518,12 +519,6 @@ const ApprovalDetail = ({
</Col>
<Col>
<Space>
{/* <Button
onClick={() => handleSave()}
disabled={!approve || selectedTab !== "raw-data" || !isEdited}
>
Save Edits
</Button> */}
<Button
type="danger"
onClick={() => handleApprove(record.id, 3)}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/approvals/Approvals.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Link } from "react-router-dom";
import { PlusSquareOutlined, CloseSquareOutlined } from "@ant-design/icons";
import { api, store } from "../../lib";
import { columnsApproval } from "./";
import ApprovalDetails from "./ApprovalsDetail";
import ApprovalDetails from "./ApprovalDetail";

const columns = [...columnsApproval, Table.EXPAND_COLUMN];

Expand Down
287 changes: 287 additions & 0 deletions frontend/src/pages/data-uploads/DataUploads.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
import React, { useEffect, useState, useMemo } from "react";
import "./style.scss";
import {
Card,
Divider,
Table,
Tabs,
Checkbox,
Button,
Modal,
Row,
Col,
Input,
} from "antd";
import { Breadcrumbs } from "../../components";
import {
PlusSquareOutlined,
CloseSquareOutlined,
FileTextFilled,
} from "@ant-design/icons";
import { api, store } from "../../lib";
import { columnsPending, columnsBatch, columnsSelected } from "./";
import UploadDetail from "./UploadDetail";
import FormDropdown from "../../components/filters/FormDropdown";
const { TextArea } = Input;

const pagePath = [
{
title: "Control Center",
link: "/control-center",
},
{
title: "Data Uploads",
},
];
const { TabPane } = Tabs;

const DataUploads = () => {
const [dataset, setDataset] = useState([]);
const [dataTab, setDataTab] = useState("pending-submission");
const [totalCount, setTotalCount] = useState(0);
const [modalButton, setModalButton] = useState(true);
const [modalVisible, setModalVisible] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [expandedKeys, setExpandedKeys] = useState([]);
const [loading, setLoading] = useState(true);
const [reload, setReload] = useState(0);
const [selectedRows, setSelectedRows] = useState([]);
const { selectedForm } = store.useState((state) => state);
const [batchName, setBatchName] = useState("");
const [comment, setComment] = useState("");
useEffect(() => {
if (selectedForm) {
setLoading(true);
let url;
if (dataTab === "pending-submission") {
url = `/form-pending-data/${selectedForm}/?page=${currentPage}`;
setModalButton(true);
} else if (dataTab === "pending-approval") {
url = `/batch/?page=${currentPage}`;
setModalButton(false);
} else if (dataTab === "approved") {
url = `batch/?page=${currentPage}&approved=true`;
setModalButton(false);
}
api
.get(url)
.then((res) => {
setDataset(res.data.data);
setTotalCount(res.data.total);
})
.catch((e) => {
console.error(e);
})
.finally(() => {
setLoading(false);
});
}
}, [dataTab, currentPage, reload, selectedForm]);

useEffect(() => {
if (selectedForm) {
setSelectedRows([]);
}
}, [selectedForm]);

const handleSelect = (row, checked) => {
const current = selectedRows.filter((s) => s.id !== row.id);
if (checked) {
setSelectedRows([...current, row]);
} else {
setSelectedRows(current);
}
};
const handleChange = (e) => {
setCurrentPage(e.current);
};

const btnBatchSelected = useMemo(() => {
if (!!selectedRows.length && modalButton) {
return (
<Button
type="primary"
onClick={() => {
setModalVisible(true);
}}
>
Batch Selected Datasets
</Button>
);
}
return "";
}, [selectedRows, modalButton]);

const sendBatch = () => {
setLoading(true);
const payload = { name: batchName, data: selectedRows.map((x) => x.id) };
api
.post(
"batch",
comment.length ? { ...payload, comment: comment } : payload
)
.then(() => {
setSelectedRows([]);
setModalVisible(false);
setLoading(false);
setDataTab("pending-approval");
})
.catch(() => {
setLoading(false);
setModalVisible(false);
});
};
return (
<div id="uploads">
<Breadcrumbs pagePath={pagePath} />
<Divider />
<FormDropdown hidden={true} />
<Card
style={{ padding: 0, minHeight: "40vh" }}
bodyStyle={{ padding: 30 }}
>
<Tabs
defaultActiveKey={dataTab}
onChange={setDataTab}
tabBarExtraContent={btnBatchSelected}
>
<TabPane tab="Pending Submission" key="pending-submission"></TabPane>
<TabPane tab="Pending Approval" key="pending-approval"></TabPane>
<TabPane tab="Approved" key="approved"></TabPane>
</Tabs>
<Table
dataSource={dataset}
onChange={handleChange}
columns={
dataTab === "pending-submission"
? [
...columnsPending,
{
title: "Batch Datasets",
render: (row) => (
<Checkbox
checked={
selectedRows.filter((s) => s.id === row.id).length
}
onChange={(e) => {
handleSelect(row, e.target.checked);
}}
/>
),
},
]
: [...columnsBatch, Table.EXPAND_COLUMN]
}
loading={loading}
pagination={{
current: currentPage,
total: totalCount,
pageSize: 10,
showSizeChanger: false,
showTotal: (total, range) =>
`Results: ${range[0]} - ${range[1]} of ${total} users`,
}}
expandedRowKeys={expandedKeys}
expandable={{
expandedRowRender: (record) => {
return (
<UploadDetail
record={record}
approve={dataTab === "pending-submission"}
setReload={setReload}
expandedParentKeys={expandedKeys}
setExpandedParentKeys={setExpandedKeys}
/>
);
},
expandIcon: ({ expanded, onExpand, record }) => {
return dataTab === "pending-submission" ? (
""
) : expanded ? (
<CloseSquareOutlined
onClick={(e) => {
setExpandedKeys(
expandedKeys.filter((k) => k !== record.id)
);
onExpand(record, e);
}}
style={{ color: "#e94b4c" }}
/>
) : (
<PlusSquareOutlined
onClick={(e) => {
setExpandedKeys([record.id]);
onExpand(record, e);
}}
style={{ color: "#7d7d7d" }}
/>
);
},
}}
rowKey="id"
/>
</Card>
<Modal
visible={modalVisible}
onCancel={() => {
setModalVisible(false);
}}
footer={
<Row align="middle">
<Col xs={24} align="left">
<div className="batch-name-field">
<label>Batch Name</label>
<Input
onChange={(e) => setBatchName(e.target.value)}
allowClear
/>
</div>
<label>Submission comment (Optional)</label>
<TextArea rows={4} onChange={(e) => setComment(e.target.value)} />
</Col>
<Col xs={12} align="left">
<Checkbox className="dev">Send a new approval request</Checkbox>
</Col>
<Col xs={12}>
<Button
className="light"
onClick={() => {
setModalVisible(false);
}}
>
Cancel
</Button>
<Button
type="primary"
onClick={sendBatch}
disabled={!batchName.length}
>
Create a new batch
</Button>
</Col>
</Row>
}
>
<p>You are about to create a Batch CSV File</p>
<p>
<FileTextFilled style={{ color: "#666666", fontSize: 64 }} />
</p>
<p>
The operation of merging datasets cannot be undone, and will Create a
new batch that will require approval from you admin
</p>
<Table
bordered
size="small"
dataSource={selectedRows}
columns={columnsSelected}
pagination={false}
scroll={{ y: 270 }}
rowKey="id"
/>
</Modal>
</div>
);
};

export default React.memo(DataUploads);
Loading