Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🤽‍♀️ Bug/admin login #1160

Merged
merged 3 commits into from
Jan 14, 2025
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: 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
Loading