From 8382f862fbaeb00c0a6d403cdddd23bc0b9ce424 Mon Sep 17 00:00:00 2001 From: Takeshi Kimata <117462761+kimatata@users.noreply.github.com> Date: Sat, 1 Jun 2024 09:48:24 +0900 Subject: [PATCH] Project member management --- backend/routes/users/find.js | 29 +++++++------- backend/seeders/seed.js | 4 +- .../[projectId]/settings/AddMemberDialog.tsx | 39 +++---------------- .../[projectId]/settings/MembersTable.tsx | 6 +-- .../[projectId]/settings/SettingsPage.tsx | 2 +- .../[projectId]/settings/membersControl.ts | 28 +++++++++++-- 6 files changed, 51 insertions(+), 57 deletions(-) diff --git a/backend/routes/users/find.js b/backend/routes/users/find.js index 92ea8b3..803c7d5 100644 --- a/backend/routes/users/find.js +++ b/backend/routes/users/find.js @@ -1,30 +1,33 @@ const express = require('express'); const router = express.Router(); const defineUser = require('../../models/users'); +const defineMember = require('../../models/members'); const { DataTypes, Op } = require('sequelize'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); const User = defineUser(sequelize, DataTypes); + const Member = defineMember(sequelize, DataTypes); router.get('/find', verifySignedIn, async (req, res) => { try { - const { search, excludeIds } = req.query; - if (!search) { - return res.status(400).json({ error: 'search text is required' }); + const { projectId, search } = req.query; + if (!projectId || !search) { + return res.status(400).json({ error: 'projectId and search text are required' }); } - let where = {}; - if (search) { - where = { - [Op.or]: [{ email: { [Op.like]: `%${search}%` } }, { username: { [Op.like]: `%${search}%` } }], - }; - } + let where = { + [Op.or]: [{ email: { [Op.like]: `%${search}%` } }, { username: { [Op.like]: `%${search}%` } }], + }; - if (excludeIds) { - const excludeIdArray = excludeIds.split(',').map((id) => parseInt(id, 10)); - where.id = { [Op.notIn]: excludeIdArray }; - } + let excludeIdArray = []; + const members = await Member.findAll({ + where: { projectId }, + attributes: ['userId'], + }); + excludeIdArray = members.map((member) => member.userId); + excludeIdArray.push(req.userId); + where.id = { [Op.notIn]: excludeIdArray }; const users = await User.findAll({ where, diff --git a/backend/seeders/seed.js b/backend/seeders/seed.js index 62af43e..07b5a5c 100644 --- a/backend/seeders/seed.js +++ b/backend/seeders/seed.js @@ -504,14 +504,14 @@ module.exports = { await queryInterface.bulkInsert('members', [ { - userId: 1, + userId: 2, projectId: 1, role: 0, createdAt: new Date(), updatedAt: new Date(), }, { - userId: 1, + userId: 3, projectId: 2, role: 0, createdAt: new Date(), diff --git a/frontend/src/app/[locale]/projects/[projectId]/settings/AddMemberDialog.tsx b/frontend/src/app/[locale]/projects/[projectId]/settings/AddMemberDialog.tsx index d698378..bb6c334 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/settings/AddMemberDialog.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/settings/AddMemberDialog.tsx @@ -4,44 +4,19 @@ import { useState, useEffect, useContext } from 'react'; import { Button, Input, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/react'; import { SettingsMessages } from '@/types/settings'; import { TokenContext } from '@/utils/TokenProvider'; -import Config from '@/config/config'; -import { MemberType, UserType } from '@/types/user'; +import { UserType } from '@/types/user'; +import { searchUsers } from './membersControl'; import CandidatesTable from './CandidatesTable'; -const apiServer = Config.apiServer; type Props = { isOpen: boolean; - members: MemberType[]; + projectId: string; onCancel: () => void; onAddMember: (memberAdded: UserType) => void; messages: SettingsMessages; }; -// User Search by username -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) { +export default function AddMemberDialog({ isOpen, projectId, onCancel, onAddMember, messages }: Props) { const context = useContext(TokenContext); const [searchText, setSearchText] = useState(''); const [candidates, setCandidates] = useState([]); @@ -57,11 +32,7 @@ export default function AddMemberDialog({ isOpen, members, onCancel, onAddMember } try { - const excludeIds = members.map((member) => { - return member.id; - }); - const excludeIdsParam = excludeIds.join(','); - const data = await searchUsers(context.token.access_token, searchText, excludeIdsParam); + const data = await searchUsers(context.token.access_token, projectId, searchText); setCandidates(data); } catch (error: any) { console.error('Error in effect:', error.message); diff --git a/frontend/src/app/[locale]/projects/[projectId]/settings/MembersTable.tsx b/frontend/src/app/[locale]/projects/[projectId]/settings/MembersTable.tsx index 0f38266..7126325 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/settings/MembersTable.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/settings/MembersTable.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo, useCallback } from 'react'; +import { useState, useMemo } from 'react'; import { Button, Table, @@ -50,7 +50,7 @@ export default function MembersTable({ members, onChangeRole, onDeleteMember, me }); }, [sortDescriptor, members]); - const renderCell = useCallback((member: UserType, columnKey: Key) => { + const renderCell = (member: UserType, columnKey: Key) => { const cellValue = member[columnKey as keyof UserType]; switch (columnKey) { @@ -93,7 +93,7 @@ export default function MembersTable({ members, onChangeRole, onDeleteMember, me default: return cellValue; } - }, []); + }; const classNames = useMemo( () => ({ diff --git a/frontend/src/app/[locale]/projects/[projectId]/settings/SettingsPage.tsx b/frontend/src/app/[locale]/projects/[projectId]/settings/SettingsPage.tsx index 9b91faf..12f67ea 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/settings/SettingsPage.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/settings/SettingsPage.tsx @@ -83,7 +83,7 @@ export default function SettingsPage({ projectId, messages, locale }: Props) { setIsDialogOpen(false)} onAddMember={handleAddMember} messages={messages} diff --git a/frontend/src/app/[locale]/projects/[projectId]/settings/membersControl.ts b/frontend/src/app/[locale]/projects/[projectId]/settings/membersControl.ts index f7c92be..d82fcbc 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/settings/membersControl.ts +++ b/frontend/src/app/[locale]/projects/[projectId]/settings/membersControl.ts @@ -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) { const fetchOptions = { method: 'POST', @@ -63,9 +86,6 @@ async function deleteMember(jwt: string, userId: string, projectId: string) { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } - const data = await response.json(); - console.log(data); - return data; } catch (error: any) { 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 };