Project member management
This commit is contained in:
@@ -1,30 +1,33 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const defineUser = require('../../models/users');
|
const defineUser = require('../../models/users');
|
||||||
|
const defineMember = require('../../models/members');
|
||||||
const { DataTypes, Op } = require('sequelize');
|
const { DataTypes, Op } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
const { verifySignedIn } = require('../../middleware/auth')(sequelize);
|
||||||
const User = defineUser(sequelize, DataTypes);
|
const User = defineUser(sequelize, DataTypes);
|
||||||
|
const Member = defineMember(sequelize, DataTypes);
|
||||||
|
|
||||||
router.get('/find', verifySignedIn, async (req, res) => {
|
router.get('/find', verifySignedIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { search, excludeIds } = req.query;
|
const { projectId, search } = req.query;
|
||||||
if (!search) {
|
if (!projectId || !search) {
|
||||||
return res.status(400).json({ error: 'search text is required' });
|
return res.status(400).json({ error: 'projectId and search text are required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let where = {};
|
let where = {
|
||||||
if (search) {
|
[Op.or]: [{ email: { [Op.like]: `%${search}%` } }, { username: { [Op.like]: `%${search}%` } }],
|
||||||
where = {
|
};
|
||||||
[Op.or]: [{ email: { [Op.like]: `%${search}%` } }, { username: { [Op.like]: `%${search}%` } }],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (excludeIds) {
|
let excludeIdArray = [];
|
||||||
const excludeIdArray = excludeIds.split(',').map((id) => parseInt(id, 10));
|
const members = await Member.findAll({
|
||||||
where.id = { [Op.notIn]: excludeIdArray };
|
where: { projectId },
|
||||||
}
|
attributes: ['userId'],
|
||||||
|
});
|
||||||
|
excludeIdArray = members.map((member) => member.userId);
|
||||||
|
excludeIdArray.push(req.userId);
|
||||||
|
where.id = { [Op.notIn]: excludeIdArray };
|
||||||
|
|
||||||
const users = await User.findAll({
|
const users = await User.findAll({
|
||||||
where,
|
where,
|
||||||
|
|||||||
@@ -504,14 +504,14 @@ module.exports = {
|
|||||||
|
|
||||||
await queryInterface.bulkInsert('members', [
|
await queryInterface.bulkInsert('members', [
|
||||||
{
|
{
|
||||||
userId: 1,
|
userId: 2,
|
||||||
projectId: 1,
|
projectId: 1,
|
||||||
role: 0,
|
role: 0,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
userId: 1,
|
userId: 3,
|
||||||
projectId: 2,
|
projectId: 2,
|
||||||
role: 0,
|
role: 0,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
|||||||
@@ -4,44 +4,19 @@ import { useState, useEffect, useContext } from 'react';
|
|||||||
import { Button, Input, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/react';
|
import { Button, Input, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/react';
|
||||||
import { SettingsMessages } from '@/types/settings';
|
import { SettingsMessages } from '@/types/settings';
|
||||||
import { TokenContext } from '@/utils/TokenProvider';
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
import Config from '@/config/config';
|
import { UserType } from '@/types/user';
|
||||||
import { MemberType, UserType } from '@/types/user';
|
import { searchUsers } from './membersControl';
|
||||||
import CandidatesTable from './CandidatesTable';
|
import CandidatesTable from './CandidatesTable';
|
||||||
const apiServer = Config.apiServer;
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
members: MemberType[];
|
projectId: string;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onAddMember: (memberAdded: UserType) => void;
|
onAddMember: (memberAdded: UserType) => void;
|
||||||
messages: SettingsMessages;
|
messages: SettingsMessages;
|
||||||
};
|
};
|
||||||
|
|
||||||
// User Search by username
|
export default function AddMemberDialog({ isOpen, projectId, onCancel, onAddMember, messages }: Props) {
|
||||||
async function searchUsers(jwt: string, text: string, excludeIdsParam: string) {
|
|
||||||
const fetchOptions = {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${jwt}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const url = `${apiServer}/users/find?search=${text}&excludeIds=${excludeIdsParam}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, fetchOptions);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Error fetching data:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AddMemberDialog({ isOpen, members, onCancel, onAddMember, messages }: Props) {
|
|
||||||
const context = useContext(TokenContext);
|
const context = useContext(TokenContext);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [candidates, setCandidates] = useState<UserType[]>([]);
|
const [candidates, setCandidates] = useState<UserType[]>([]);
|
||||||
@@ -57,11 +32,7 @@ export default function AddMemberDialog({ isOpen, members, onCancel, onAddMember
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const excludeIds = members.map((member) => {
|
const data = await searchUsers(context.token.access_token, projectId, searchText);
|
||||||
return member.id;
|
|
||||||
});
|
|
||||||
const excludeIdsParam = excludeIds.join(',');
|
|
||||||
const data = await searchUsers(context.token.access_token, searchText, excludeIdsParam);
|
|
||||||
setCandidates(data);
|
setCandidates(data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error in effect:', error.message);
|
console.error('Error in effect:', error.message);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo, useCallback } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Table,
|
Table,
|
||||||
@@ -50,7 +50,7 @@ export default function MembersTable({ members, onChangeRole, onDeleteMember, me
|
|||||||
});
|
});
|
||||||
}, [sortDescriptor, members]);
|
}, [sortDescriptor, members]);
|
||||||
|
|
||||||
const renderCell = useCallback((member: UserType, columnKey: Key) => {
|
const renderCell = (member: UserType, columnKey: Key) => {
|
||||||
const cellValue = member[columnKey as keyof UserType];
|
const cellValue = member[columnKey as keyof UserType];
|
||||||
|
|
||||||
switch (columnKey) {
|
switch (columnKey) {
|
||||||
@@ -93,7 +93,7 @@ export default function MembersTable({ members, onChangeRole, onDeleteMember, me
|
|||||||
default:
|
default:
|
||||||
return cellValue;
|
return cellValue;
|
||||||
}
|
}
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
const classNames = useMemo(
|
const classNames = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export default function SettingsPage({ projectId, messages, locale }: Props) {
|
|||||||
|
|
||||||
<AddMemberDialog
|
<AddMemberDialog
|
||||||
isOpen={isDialogOpen}
|
isOpen={isDialogOpen}
|
||||||
members={members}
|
projectId={projectId}
|
||||||
onCancel={() => setIsDialogOpen(false)}
|
onCancel={() => setIsDialogOpen(false)}
|
||||||
onAddMember={handleAddMember}
|
onAddMember={handleAddMember}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
|
|||||||
@@ -24,6 +24,29 @@ async function fetchProjectMembers(jwt: string, projectId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function searchUsers(jwt: string, projectId: string, searchText: string) {
|
||||||
|
const fetchOptions = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = `${apiServer}/users/find?projectId=${projectId}&search=${searchText}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, fetchOptions);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error fetching data:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function addMember(jwt: string, userId: string, projectId: string) {
|
async function addMember(jwt: string, userId: string, projectId: string) {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -63,9 +86,6 @@ async function deleteMember(jwt: string, userId: string, projectId: string) {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
|
||||||
console.log(data);
|
|
||||||
return data;
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error fetching data:', error.message);
|
console.error('Error fetching data:', error.message);
|
||||||
}
|
}
|
||||||
@@ -94,4 +114,4 @@ async function updateMember(jwt: string, userId: string, projectId: string, role
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { fetchProjectMembers, addMember, deleteMember, updateMember };
|
export { fetchProjectMembers, searchUsers, addMember, deleteMember, updateMember };
|
||||||
|
|||||||
Reference in New Issue
Block a user