Skip to content

Commit

Permalink
🤽‍♀️ Bug/admin login (#1160)
Browse files Browse the repository at this point in the history
* Added sort order key

* fix lint issue

* Add DND Feature
  • Loading branch information
hv2308 authored Jan 14, 2025
1 parent 7835e59 commit 7c927d6
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 11 deletions.
4 changes: 3 additions & 1 deletion backend/api/models/Training.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ class Training(Document, Mixin):
partner_id = StringField(required=False)
mentor_id = ListField(StringField(), required=False)
mentee_id = ListField(StringField(), required=False)
sort_order = IntField(required=False, default=0)

def __repr__(self):
return f"""<Training : {self.name}
\n name: {self.name}
\n url: {self.url}
\n description: {self.description}
\n date_submitted: {self.date_submitted}>"""
\n date_submitted: {self.date_submitted}
\n sort_order: {self.sort_order}>"""
65 changes: 65 additions & 0 deletions backend/api/views/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,71 @@ def get_trainings(role):

return create_response(data={"trainings": result})

@training.route("/update_multiple", methods=["PATCH"])
def update_multiple_trainings():
data = request.json.get("trainings", [])
if not data:
return create_response(status=400, message="No trainings provided for update")

updated_trainings = []
failed_updates = []

for training in data:
training_id = training.get("id")
update_data = training.get("updated_data", {})

if not training_id:
failed_updates.append({"error": "Training ID is required"})
continue

try:
training_id = ObjectId(training_id)
except Exception as e:
failed_updates.append({"error": f"Invalid ID format: {str(e)}"})
continue

train = Training.objects(id=training_id).first()
if not train:
failed_updates.append({"error": f"Training with ID {training_id} not found"})
continue

train_data = train.to_mongo().to_dict()

# Exclude `_id` and `sort_order` for comparison
train_data.pop("_id", None)
existing_sort_order = train_data.pop("sort_order", None)
updated_sort_order = update_data.get("sort_order")

# Compare all fields except `sort_order`
other_fields_match = all(
train_data.get(key) == value
for key, value in update_data.items()
if key != "sort_order"
)

if not other_fields_match:
failed_updates.append({
"error": f"Only sort_order can be updated. Mismatched fields for ID {training_id}"
})
continue

# Update sort_order if it's different
if updated_sort_order != existing_sort_order:
train.update(sort_order=updated_sort_order)
updated_trainings.append(train.reload())
else:
failed_updates.append({
"error": f"No changes in sort_order for ID {training_id}"
})

result = {
"updated_trainings": [json.loads(t.to_json()) for t in updated_trainings],
"failed_updates": failed_updates,
}

return create_response(data=result)



@training.route("/<string:id>", methods=["DELETE"])
@admin_only
Expand Down
3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"private": true,
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@emotion/css": "^11.11.2",
"@jitsi/react-sdk": "^1.4.0",
"@reduxjs/toolkit": "^1.9.3",
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/components/TrainingList.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,13 @@ const TrainingList = (props) => {
} else {
hub_user_id = user._id.$oid;
}
setTrainingData(trains.filter((x) => x.hub_id == hub_user_id));
setTrainingData(
trains
.sort((a, b) => a.sort_order - b.sort_order)
.filter((x) => x.hub_id == hub_user_id)
);
} else {
setTrainingData(trains);
setTrainingData(trains.sort((a, b) => a.sort_order - b.sort_order));
}
setLoading(false);
setFlag(!flag);
Expand Down
145 changes: 139 additions & 6 deletions frontend/src/components/pages/AdminTraining.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useContext, useEffect, useMemo, useState } from "react";
import {
deleteTrainbyId,
downloadBlob,
Expand All @@ -9,6 +9,7 @@ import {
fetchAccounts,
fetchPartners,
newTrainCreate,
updateTrainings,
} from "utils/api";
import { ACCOUNT_TYPE, I18N_LANGUAGES, TRAINING_TYPE } from "utils/consts";
import { HubsDropdown } from "../AdminDropdowns";
Expand All @@ -26,6 +27,7 @@ import {
import {
DeleteOutlined,
EditOutlined,
HolderOutlined,
PlusCircleOutlined,
TeamOutlined,
} from "@ant-design/icons";
Expand All @@ -35,6 +37,69 @@ import "components/css/Training.scss";
import AdminDownloadDropdown from "../AdminDownloadDropdown";
import TrainingTranslationModal from "../TrainingTranslationModal";
import UpdateTrainingForm from "../UpdateTrainingModal";
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { DndContext } from "@dnd-kit/core";
import { CSS } from "@dnd-kit/utilities";
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";

const RowContext = React.createContext({});
const DragHandle = () => {
const { setActivatorNodeRef, listeners } = useContext(RowContext);
return (
<Button
type="text"
size="small"
icon={<HolderOutlined />}
style={{
cursor: "move",
}}
ref={setActivatorNodeRef}
{...listeners}
/>
);
};

const Row = (props) => {
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id: props["data-row-key"],
});
const style = {
...props.style,
transform: CSS.Translate.toString(transform),
transition,
...(isDragging
? {
position: "relative",
zIndex: 9999,
}
: {}),
};
const contextValue = useMemo(
() => ({
setActivatorNodeRef,
listeners,
}),
[setActivatorNodeRef, listeners]
);
return (
<RowContext.Provider value={contextValue}>
<tr {...props} ref={setNodeRef} style={style} {...attributes} />
</RowContext.Provider>
);
};

const AdminTraining = () => {
const [role, setRole] = useState(ACCOUNT_TYPE.MENTEE);
Expand Down Expand Up @@ -94,7 +159,7 @@ const AdminTraining = () => {

const handleResetFilters = () => {
setResetFilters(!resetFilters);
setTrainingData(allData);
setTrainingData(allData.sort((a, b) => a.sort_order - b.sort_order));
};

const onFinishTrainingForm = async (values, isNewTraining) => {
Expand Down Expand Up @@ -178,6 +243,43 @@ const AdminTraining = () => {
}
};

const onDragEnd = async ({ active, over }) => {
if (active.id !== over?.id) {
const updatedTrainingData = [...trainingData];
const activeIndex = updatedTrainingData.findIndex(
(record) => record.id === active?.id
);
const overIndex = updatedTrainingData.findIndex(
(record) => record.id === over?.id
);
const movedTrainingData = arrayMove(
updatedTrainingData,
activeIndex,
overIndex
);
const updatedData = movedTrainingData.map((item, index) => ({
id: item.id,
updated_data: { sort_order: index },
}));
setLoading(true);
const response = await updatedTraningData(updatedData);
if (response) {
setLoading(false);
}
setTrainingData(movedTrainingData);
}
};

const updatedTraningData = async (data) => {
try {
await updateTrainings(data);
return "success";
} catch (err) {
console.error(err);
return err;
}
};

const getAvailableLangs = (record) => {
if (!record?.translations) return [I18N_LANGUAGES[0]];
let items = I18N_LANGUAGES.filter((lang) => {
Expand All @@ -201,8 +303,8 @@ const AdminTraining = () => {
setLoading(true);
let newData = await getTrainings(role);
if (newData) {
setTrainingData(newData);
setAllData(newData);
setTrainingData(newData.sort((a, b) => a.sort_order - b.sort_order));
setAllData(newData.sort((a, b) => a.sort_order - b.sort_order));
} else {
setTrainingData([]);
setAllData([]);
Expand All @@ -217,6 +319,12 @@ const AdminTraining = () => {
}, [role, reload]);

const columns = [
{
key: "sort",
align: "center",
width: 80,
render: () => <DragHandle />,
},
{
title: "Name",
dataIndex: "name",
Expand Down Expand Up @@ -325,7 +433,11 @@ const AdminTraining = () => {

const searchbyHub = (hub_id) => {
if (role === ACCOUNT_TYPE.HUB) {
setTrainingData(allData.filter((x) => x.hub_id == hub_id));
setTrainingData(
allData
.sort((a, b) => a.sort_order - b.sort_order)
.filter((x) => x.hub_id == hub_id)
);
}
};

Expand All @@ -352,6 +464,8 @@ const AdminTraining = () => {
},
];

console.log(trainingData);

return (
<div className="trains">
<Tabs
Expand Down Expand Up @@ -386,7 +500,26 @@ const AdminTraining = () => {
</div>
<Spin spinning={translateLoading}>
<Skeleton loading={loading} active>
<Table columns={columns} dataSource={trainingData} />
<DndContext
modifiers={[restrictToVerticalAxis]}
onDragEnd={onDragEnd}
>
<SortableContext
items={trainingData.map((i) => i.id)}
strategy={verticalListSortingStrategy}
>
<Table
rowKey="id"
components={{
body: {
row: Row,
},
}}
columns={columns}
dataSource={trainingData}
/>
</SortableContext>
</DndContext>
</Skeleton>
</Spin>
<TrainingTranslationModal
Expand Down
24 changes: 22 additions & 2 deletions frontend/src/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ const authPut = async (url, data, config) =>
headers: { Authorization: await getUserIdToken() },
});

const authPatch = async (url, data, config) =>
instance.patch(url, data, {
...config,
headers: { Authorization: await getUserIdToken() },
});

const authDelete = async (url, config) =>
instance.delete(url, {
...config,
Expand Down Expand Up @@ -277,13 +283,27 @@ export const getTrainings = async (
}).catch(console.error);
const trains = res.data.result.trainings;
let newTrain = [];
let seenOids = new Set();
for (let train of trains) {
train.id = train._id["$oid"];
newTrain.push(train);
const oid = train._id["$oid"];
if (!seenOids.has(oid)) {
train.id = oid;
newTrain.push(train);
seenOids.add(oid);
}
}
return newTrain;
};

export const updateTrainings = async (data) => {
const requestExtension = `/training/update_multiple`;
const res = await authPatch(requestExtension, {
trainings: data,
}).catch(console.error);
const trains = res.data.result.trainings;
return trains;
};

export const getNotifys = async () => {
const requestExtension = `/notifys/`;
const res = await authGet(requestExtension).catch(console.error);
Expand Down

0 comments on commit 7c927d6

Please sign in to comment.