feat: add project settings page
This commit is contained in:
@@ -9,7 +9,14 @@ type Props = {
|
|||||||
deleteText: string;
|
deleteText: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ProjectDialog({ isOpen, onCancel, onConfirm, closeText, confirmText, deleteText }: Props) {
|
export default function DeleteConfirmDialog({
|
||||||
|
isOpen,
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
closeText,
|
||||||
|
confirmText,
|
||||||
|
deleteText,
|
||||||
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@
|
|||||||
"home": "Home",
|
"home": "Home",
|
||||||
"test_cases": "Test Cases",
|
"test_cases": "Test Cases",
|
||||||
"test_runs": "Test Runs",
|
"test_runs": "Test Runs",
|
||||||
|
"members": "Members",
|
||||||
"settings": "Settings"
|
"settings": "Settings"
|
||||||
},
|
},
|
||||||
"Home": {
|
"Home": {
|
||||||
@@ -260,7 +261,7 @@
|
|||||||
"exclude_from_run": "Exclude from run",
|
"exclude_from_run": "Exclude from run",
|
||||||
"no_cases_found": "No cases found"
|
"no_cases_found": "No cases found"
|
||||||
},
|
},
|
||||||
"Settings": {
|
"Members": {
|
||||||
"member_management": "Member Management",
|
"member_management": "Member Management",
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
@@ -276,5 +277,21 @@
|
|||||||
"user_name_or_email": "User name or email",
|
"user_name_or_email": "User name or email",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"add": "Add"
|
"add": "Add"
|
||||||
|
},
|
||||||
|
"Settings": {
|
||||||
|
"project_management": "Project Management",
|
||||||
|
"project_name": "Project Name",
|
||||||
|
"project_detail": "Project Detail",
|
||||||
|
"edit_project": "Edit Project",
|
||||||
|
"project": "Project",
|
||||||
|
"publicity": "Publicity",
|
||||||
|
"public": "Public",
|
||||||
|
"private": "Private",
|
||||||
|
"if_you_make_public": "Making a project public makes it visible to users who are not project members.",
|
||||||
|
"update": "Update",
|
||||||
|
"delete_project": "Delete Project",
|
||||||
|
"delete": "Delete",
|
||||||
|
"close": "Close",
|
||||||
|
"are_you_sure": "Are you sure you want to delete the project?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,7 @@
|
|||||||
"home": "ホーム",
|
"home": "ホーム",
|
||||||
"test_cases": "テストケース",
|
"test_cases": "テストケース",
|
||||||
"test_runs": "テストラン",
|
"test_runs": "テストラン",
|
||||||
|
"members": "メンバー",
|
||||||
"settings": "設定"
|
"settings": "設定"
|
||||||
},
|
},
|
||||||
"Home": {
|
"Home": {
|
||||||
@@ -259,7 +260,7 @@
|
|||||||
"exclude_from_run": "テストランから除外する",
|
"exclude_from_run": "テストランから除外する",
|
||||||
"no_cases_found": "テストケースが見つかりません"
|
"no_cases_found": "テストケースが見つかりません"
|
||||||
},
|
},
|
||||||
"Settings": {
|
"Members": {
|
||||||
"member_management": "メンバー管理",
|
"member_management": "メンバー管理",
|
||||||
"avatar": "アバター",
|
"avatar": "アバター",
|
||||||
"email": "メールアドレス",
|
"email": "メールアドレス",
|
||||||
@@ -275,5 +276,21 @@
|
|||||||
"user_name_or_email": "ユーザー名またはメールアドレス",
|
"user_name_or_email": "ユーザー名またはメールアドレス",
|
||||||
"close": "閉じる",
|
"close": "閉じる",
|
||||||
"add": "追加"
|
"add": "追加"
|
||||||
|
},
|
||||||
|
"Settings": {
|
||||||
|
"project_management": "プロジェクト管理",
|
||||||
|
"project_name": "プロジェクト名",
|
||||||
|
"project_detail": "プロジェクト詳細",
|
||||||
|
"edit_project": "プロジェクトの編集",
|
||||||
|
"project": "プロジェクト",
|
||||||
|
"publicity": "公開",
|
||||||
|
"public": "パブリック",
|
||||||
|
"private": "プライベート",
|
||||||
|
"if_you_make_public": "プロジェクトをパブリックにすると、プロジェクトメンバーではないユーザーからも見えるようになります。",
|
||||||
|
"update": "更新",
|
||||||
|
"delete_project": "プロジェクトの削除",
|
||||||
|
"delete": "削除",
|
||||||
|
"close": "閉じる",
|
||||||
|
"are_you_sure": "プロジェクトを削除してもよろしいですか?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { Plus } from 'lucide-react';
|
|||||||
import { TokenContext } from '@/utils/TokenProvider';
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
import { ProjectType, ProjectsMessages } from '@/types/project';
|
import { ProjectType, ProjectsMessages } from '@/types/project';
|
||||||
import ProjectsTable from './ProjectsTable';
|
import ProjectsTable from './ProjectsTable';
|
||||||
import ProjectDialog from './ProjectDialog';
|
import ProjectDialog from '@/components/ProjectDialog';
|
||||||
import { fetchProjects, createProject, updateProject, deleteProject } from './projectsControl';
|
import { fetchProjects, createProject, updateProject, deleteProject } from '@/utils/projectsControl';
|
||||||
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Listbox, ListboxItem } from '@nextui-org/react';
|
import { Listbox, ListboxItem } from '@nextui-org/react';
|
||||||
import { Home, Files, FlaskConical, Settings } from 'lucide-react';
|
import { Home, Files, FlaskConical, Users, Settings } from 'lucide-react';
|
||||||
import { usePathname, useRouter } from '@/src/navigation';
|
import { usePathname, useRouter } from '@/src/navigation';
|
||||||
import useGetCurrentIds from '@/utils/useGetCurrentIds';
|
import useGetCurrentIds from '@/utils/useGetCurrentIds';
|
||||||
import { ProjectMessages } from '@/types/project';
|
import { ProjectMessages } from '@/types/project';
|
||||||
@@ -27,6 +27,8 @@ export default function Sidebar({ messages, locale }: Props) {
|
|||||||
router.push(`/projects/${projectId}/folders`, { locale: locale });
|
router.push(`/projects/${projectId}/folders`, { locale: locale });
|
||||||
} else if (key === 'runs') {
|
} else if (key === 'runs') {
|
||||||
router.push(`/projects/${projectId}/runs`, { locale: locale });
|
router.push(`/projects/${projectId}/runs`, { locale: locale });
|
||||||
|
} else if (key === 'members') {
|
||||||
|
router.push(`/projects/${projectId}/members`, { locale: locale });
|
||||||
} else if (key === 'settings') {
|
} else if (key === 'settings') {
|
||||||
router.push(`/projects/${projectId}/settings`, { locale: locale });
|
router.push(`/projects/${projectId}/settings`, { locale: locale });
|
||||||
}
|
}
|
||||||
@@ -40,6 +42,8 @@ export default function Sidebar({ messages, locale }: Props) {
|
|||||||
setCurrentTab('cases');
|
setCurrentTab('cases');
|
||||||
} else if (currentPath.includes('runs')) {
|
} else if (currentPath.includes('runs')) {
|
||||||
setCurrentTab('runs');
|
setCurrentTab('runs');
|
||||||
|
} else if (currentPath.includes('members')) {
|
||||||
|
setCurrentTab('members');
|
||||||
} else if (currentPath.includes('settings')) {
|
} else if (currentPath.includes('settings')) {
|
||||||
setCurrentTab('settings');
|
setCurrentTab('settings');
|
||||||
}
|
}
|
||||||
@@ -64,6 +68,11 @@ export default function Sidebar({ messages, locale }: Props) {
|
|||||||
text: messages.testRuns,
|
text: messages.testRuns,
|
||||||
startContent: <FlaskConical strokeWidth={1} size={20} />,
|
startContent: <FlaskConical strokeWidth={1} size={20} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'members',
|
||||||
|
text: messages.members,
|
||||||
|
startContent: <Users strokeWidth={1} size={20} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'settings',
|
key: 'settings',
|
||||||
text: messages.settings,
|
text: messages.settings,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ type Props = {
|
|||||||
messages: HomeMessages;
|
messages: HomeMessages;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Home({ projectId, messages }: Props) {
|
export function ProjectHome({ projectId, messages }: Props) {
|
||||||
const context = useContext(TokenContext);
|
const context = useContext(TokenContext);
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
const [project, setProject] = useState({
|
const [project, setProject] = useState({
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Home } from './home';
|
import { ProjectHome } from './ProjectHome';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
export type HomeMessages = {
|
export type HomeMessages = {
|
||||||
@@ -69,7 +69,7 @@ export default function Page({ params }: { params: { projectId: string } }) {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Home projectId={params.projectId} messages={messages} />
|
<ProjectHome projectId={params.projectId} messages={messages} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default function SidebarLayout({
|
|||||||
home: t('home'),
|
home: t('home'),
|
||||||
testCases: t('test_cases'),
|
testCases: t('test_cases'),
|
||||||
testRuns: t('test_runs'),
|
testRuns: t('test_runs'),
|
||||||
|
members: t('members'),
|
||||||
settings: t('settings'),
|
settings: t('settings'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
'use client';
|
||||||
|
import React from 'react';
|
||||||
|
import { useState, useEffect, useContext } from 'react';
|
||||||
|
import { Button } from '@nextui-org/react';
|
||||||
|
import { Plus } from 'lucide-react';
|
||||||
|
import { MemberType, UserType } from '@/types/user';
|
||||||
|
import { MembersMessages } from '@/types/members';
|
||||||
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
|
import MembersTable from './MembersTable';
|
||||||
|
import AddMemberDialog from './AddMemberDialog';
|
||||||
|
import { fetchProjectMembers, addMember, deleteMember, updateMember } from './membersControl';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
projectId: string;
|
||||||
|
messages: MembersMessages;
|
||||||
|
locale: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function MembersPage({ projectId, messages, locale }: Props) {
|
||||||
|
const context = useContext(TokenContext);
|
||||||
|
const [members, setMembers] = useState<MemberType[]>([]);
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchDataEffect() {
|
||||||
|
if (!context.isSignedIn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await fetchProjectMembers(context.token.access_token, projectId);
|
||||||
|
setMembers(data);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error in effect:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchDataEffect();
|
||||||
|
}, [context]);
|
||||||
|
|
||||||
|
const handleAddMember = async (userAdded: UserType) => {
|
||||||
|
const newMember = await addMember(context.token.access_token, userAdded.id, projectId);
|
||||||
|
newMember.User = userAdded;
|
||||||
|
const updateMembers = [...members];
|
||||||
|
updateMembers.push(newMember);
|
||||||
|
setMembers(updateMembers);
|
||||||
|
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteMember = async (userDeleted: UserType) => {
|
||||||
|
await deleteMember(context.token.access_token, userDeleted.id, projectId);
|
||||||
|
setMembers(members.filter((member) => member.User.id !== userDeleted.id));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeRole = async (userEdit: UserType, role: number) => {
|
||||||
|
await updateMember(context.token.access_token, userEdit.id, projectId, role);
|
||||||
|
setMembers((prevMembers) => {
|
||||||
|
return prevMembers.map((member) => {
|
||||||
|
if (member.User.id === userEdit.id) {
|
||||||
|
return { ...member, role: role };
|
||||||
|
}
|
||||||
|
return member;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto max-w-3xl pt-6 px-6 flex-grow">
|
||||||
|
<div className="w-full p-3 flex items-center justify-between">
|
||||||
|
<h3 className="font-bold">{messages.memberManagement}</h3>
|
||||||
|
<Button
|
||||||
|
startContent={<Plus size={16} />}
|
||||||
|
size="sm"
|
||||||
|
color="primary"
|
||||||
|
isDisabled={!context.isProjectManager(Number(projectId))}
|
||||||
|
onClick={() => setIsDialogOpen(true)}
|
||||||
|
>
|
||||||
|
{messages.addMember}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MembersTable
|
||||||
|
members={members}
|
||||||
|
isDisabled={!context.isProjectManager(Number(projectId))}
|
||||||
|
onChangeRole={handleChangeRole}
|
||||||
|
onDeleteMember={handleDeleteMember}
|
||||||
|
messages={messages}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddMemberDialog
|
||||||
|
isOpen={isDialogOpen}
|
||||||
|
projectId={projectId}
|
||||||
|
onCancel={() => setIsDialogOpen(false)}
|
||||||
|
onAddMember={handleAddMember}
|
||||||
|
messages={messages}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import MembersPage from './MembersPage';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
export default function Page({ params }: { params: { projectId: string; locale: string } }) {
|
||||||
|
const t = useTranslations('Members');
|
||||||
|
const messages = {
|
||||||
|
memberManagement: t('member_management'),
|
||||||
|
avatar: t('avatar'),
|
||||||
|
email: t('email'),
|
||||||
|
username: t('username'),
|
||||||
|
role: t('role'),
|
||||||
|
manager: t('manager'),
|
||||||
|
developer: t('developer'),
|
||||||
|
reporter: t('reporter'),
|
||||||
|
delete: t('delete'),
|
||||||
|
deleteMember: t('deleteMember'),
|
||||||
|
noMembersFound: t('no_members_found'),
|
||||||
|
addMember: t('add_member'),
|
||||||
|
userNameOrEmail: t('user_name_or_email'),
|
||||||
|
close: t('close'),
|
||||||
|
add: t('add'),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MembersPage projectId={params.projectId} messages={messages} locale={params.locale} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useState, useEffect, useContext } from 'react';
|
import { useState, useEffect, useContext } from 'react';
|
||||||
import { Button } from '@nextui-org/react';
|
import { Button, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from '@nextui-org/react';
|
||||||
import { Plus } from 'lucide-react';
|
import { Pencil, Trash } from 'lucide-react';
|
||||||
import { MemberType, UserType } from '@/types/user';
|
|
||||||
import { SettingsMessages } from '@/types/settings';
|
import { SettingsMessages } from '@/types/settings';
|
||||||
import { TokenContext } from '@/utils/TokenProvider';
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
import MembersTable from './MembersTable';
|
import { deleteProject, fetchProject, updateProject } from '@/utils/projectsControl';
|
||||||
import AddMemberDialog from './AddMemberDialog';
|
import { ProjectType } from '@/types/project';
|
||||||
import { fetchProjectMembers, addMember, deleteMember, updateMember } from './membersControl';
|
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
||||||
|
import { useRouter } from '@/src/navigation';
|
||||||
|
import ProjectDialog from '@/components/ProjectDialog';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -18,8 +19,18 @@ type Props = {
|
|||||||
|
|
||||||
export default function SettingsPage({ projectId, messages, locale }: Props) {
|
export default function SettingsPage({ projectId, messages, locale }: Props) {
|
||||||
const context = useContext(TokenContext);
|
const context = useContext(TokenContext);
|
||||||
const [members, setMembers] = useState<MemberType[]>([]);
|
const router = useRouter();
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [project, setProject] = useState<ProjectType>({
|
||||||
|
id: 0,
|
||||||
|
name: '',
|
||||||
|
detail: '',
|
||||||
|
isPublic: false,
|
||||||
|
userId: 0,
|
||||||
|
createdAt: '',
|
||||||
|
updatedAt: '',
|
||||||
|
Folders: [],
|
||||||
|
Runs: [],
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchDataEffect() {
|
async function fetchDataEffect() {
|
||||||
@@ -28,8 +39,8 @@ export default function SettingsPage({ projectId, messages, locale }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await fetchProjectMembers(context.token.access_token, projectId);
|
const data = await fetchProject(context.token.access_token, Number(projectId));
|
||||||
setMembers(data);
|
setProject(data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error in effect:', error.message);
|
console.error('Error in effect:', error.message);
|
||||||
}
|
}
|
||||||
@@ -38,62 +49,87 @@ export default function SettingsPage({ projectId, messages, locale }: Props) {
|
|||||||
fetchDataEffect();
|
fetchDataEffect();
|
||||||
}, [context]);
|
}, [context]);
|
||||||
|
|
||||||
const handleAddMember = async (userAdded: UserType) => {
|
// project dialog
|
||||||
const newMember = await addMember(context.token.access_token, userAdded.id, projectId);
|
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false);
|
||||||
newMember.User = userAdded;
|
const onSubmit = async (name: string, detail: string, isPublic: boolean) => {
|
||||||
const updateMembers = [...members];
|
const updatedProject = await updateProject(context.token.access_token, project.id, name, detail, isPublic);
|
||||||
updateMembers.push(newMember);
|
setProject(updatedProject);
|
||||||
setMembers(updateMembers);
|
setIsProjectDialogOpen(false);
|
||||||
|
|
||||||
setIsDialogOpen(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteMember = async (userDeleted: UserType) => {
|
// delete confirm dialog
|
||||||
await deleteMember(context.token.access_token, userDeleted.id, projectId);
|
const [isDeleteConfirmDialogOpen, setIsDeleteConfirmDialogOpen] = useState(false);
|
||||||
setMembers(members.filter((member) => member.User.id !== userDeleted.id));
|
const onConfirm = async () => {
|
||||||
};
|
await deleteProject(context.token.access_token, Number(projectId));
|
||||||
|
setIsDeleteConfirmDialogOpen(false);
|
||||||
const handleChangeRole = async (userEdit: UserType, role: number) => {
|
router.push(`/projects/`, { locale: locale });
|
||||||
await updateMember(context.token.access_token, userEdit.id, projectId, role);
|
|
||||||
setMembers((prevMembers) => {
|
|
||||||
return prevMembers.map((member) => {
|
|
||||||
if (member.User.id === userEdit.id) {
|
|
||||||
return { ...member, role: role };
|
|
||||||
}
|
|
||||||
return member;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto max-w-3xl pt-16 px-6 flex-grow">
|
<div className="container mx-auto max-w-3xl pt-6 px-6 flex-grow">
|
||||||
<div className="w-full p-3 flex items-center justify-between">
|
<div className="w-full p-3 flex items-center justify-between">
|
||||||
<h3 className="font-bold">{messages.memberManagement}</h3>
|
<h3 className="font-bold">{messages.projectManagement}</h3>
|
||||||
|
<div>
|
||||||
<Button
|
<Button
|
||||||
startContent={<Plus size={16} />}
|
startContent={<Trash size={16} />}
|
||||||
|
size="sm"
|
||||||
|
color="danger"
|
||||||
|
isDisabled={!context.isProjectManager(Number(projectId))}
|
||||||
|
onClick={() => setIsDeleteConfirmDialogOpen(true)}
|
||||||
|
>
|
||||||
|
{messages.deleteProject}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
startContent={<Pencil size={16} />}
|
||||||
size="sm"
|
size="sm"
|
||||||
color="primary"
|
color="primary"
|
||||||
isDisabled={!context.isProjectManager(Number(projectId))}
|
isDisabled={!context.isProjectManager(Number(projectId))}
|
||||||
onClick={() => setIsDialogOpen(true)}
|
onClick={() => setIsProjectDialogOpen(true)}
|
||||||
|
className="ms-2"
|
||||||
>
|
>
|
||||||
{messages.addMember}
|
{messages.editProject}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MembersTable
|
<div className="w-full p-3">
|
||||||
members={members}
|
<Table hideHeader aria-label="Example static collection table">
|
||||||
isDisabled={!context.isProjectManager(Number(projectId))}
|
<TableHeader>
|
||||||
onChangeRole={handleChangeRole}
|
<TableColumn>dummy</TableColumn>
|
||||||
onDeleteMember={handleDeleteMember}
|
<TableColumn>dummy</TableColumn>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow key="1">
|
||||||
|
<TableCell>{messages.projectName}</TableCell>
|
||||||
|
<TableCell>{project.name}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow key="2">
|
||||||
|
<TableCell>{messages.projectDetail}</TableCell>
|
||||||
|
<TableCell>{project.detail}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow key="3">
|
||||||
|
<TableCell>{messages.publicity}</TableCell>
|
||||||
|
<TableCell>{project.isPublic ? messages.public : messages.private}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ProjectDialog
|
||||||
|
isOpen={isProjectDialogOpen}
|
||||||
|
editingProject={project}
|
||||||
|
onCancel={() => setIsProjectDialogOpen(false)}
|
||||||
|
onSubmit={onSubmit}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AddMemberDialog
|
<DeleteConfirmDialog
|
||||||
isOpen={isDialogOpen}
|
isOpen={isDeleteConfirmDialogOpen}
|
||||||
projectId={projectId}
|
onCancel={() => setIsDeleteConfirmDialogOpen(false)}
|
||||||
onCancel={() => setIsDialogOpen(false)}
|
onConfirm={onConfirm}
|
||||||
onAddMember={handleAddMember}
|
closeText={messages.close}
|
||||||
messages={messages}
|
confirmText={messages.areYouSure}
|
||||||
|
deleteText={messages.delete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,21 +4,20 @@ import { useTranslations } from 'next-intl';
|
|||||||
export default function Page({ params }: { params: { projectId: string; locale: string } }) {
|
export default function Page({ params }: { params: { projectId: string; locale: string } }) {
|
||||||
const t = useTranslations('Settings');
|
const t = useTranslations('Settings');
|
||||||
const messages = {
|
const messages = {
|
||||||
memberManagement: t('member_management'),
|
projectManagement: t('project_management'),
|
||||||
avatar: t('avatar'),
|
projectName: t('project_name'),
|
||||||
email: t('email'),
|
projectDetail: t('project_detail'),
|
||||||
username: t('username'),
|
editProject: t('edit_project'),
|
||||||
role: t('role'),
|
project: t('project'),
|
||||||
manager: t('manager'),
|
ifYouMakePublic: t('if_you_make_public'),
|
||||||
developer: t('developer'),
|
public: t('public'),
|
||||||
reporter: t('reporter'),
|
publicity: t('publicity'),
|
||||||
|
private: t('private'),
|
||||||
|
update: t('update'),
|
||||||
|
deleteProject: t('delete_project'),
|
||||||
delete: t('delete'),
|
delete: t('delete'),
|
||||||
deleteMember: t('deleteMember'),
|
|
||||||
noMembersFound: t('no_members_found'),
|
|
||||||
addMember: t('add_member'),
|
|
||||||
userNameOrEmail: t('user_name_or_email'),
|
|
||||||
close: t('close'),
|
close: t('close'),
|
||||||
add: t('add'),
|
areYouSure: t('are_you_sure'),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
17
frontend/types/member.ts
Normal file
17
frontend/types/member.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export type MembersMessages = {
|
||||||
|
memberManagement: string;
|
||||||
|
avatar: string;
|
||||||
|
email: string;
|
||||||
|
username: string;
|
||||||
|
role: string;
|
||||||
|
manager: string;
|
||||||
|
developer: string;
|
||||||
|
reporter: string;
|
||||||
|
delete: string;
|
||||||
|
deleteMember: string;
|
||||||
|
noMembersFound: string;
|
||||||
|
addMember: string;
|
||||||
|
userNameOrEmail: string;
|
||||||
|
close: string;
|
||||||
|
add: string;
|
||||||
|
};
|
||||||
@@ -5,7 +5,7 @@ export type ProjectType = {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
detail: string;
|
detail: string;
|
||||||
isPublic: string;
|
isPublic: boolean;
|
||||||
userId: number;
|
userId: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
export type SettingsMessages = {
|
export type SettingsMessages = {
|
||||||
memberManagement: string;
|
projectManagement: string;
|
||||||
avatar: string;
|
projectName: string;
|
||||||
email: string;
|
projectDetail: string;
|
||||||
username: string;
|
editProject: string;
|
||||||
role: string;
|
project: string;
|
||||||
manager: string;
|
publicity: string;
|
||||||
developer: string;
|
public: string;
|
||||||
reporter: string;
|
private: string;
|
||||||
|
ifYouMakePublic: string;
|
||||||
|
update: string;
|
||||||
|
deleteProject: string;
|
||||||
delete: string;
|
delete: string;
|
||||||
deleteMember: string;
|
|
||||||
noMembersFound: string;
|
|
||||||
addMember: string;
|
|
||||||
userNameOrEmail: string;
|
|
||||||
close: string;
|
close: string;
|
||||||
add: string;
|
areYouSure: string;
|
||||||
};
|
};
|
||||||
|
|||||||
149
frontend/utils/projectsControl.ts
Normal file
149
frontend/utils/projectsControl.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import Config from '@/config/config';
|
||||||
|
const apiServer = Config.apiServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch project
|
||||||
|
*/
|
||||||
|
async function fetchProject(jwt: string, projectId: number) {
|
||||||
|
const url = `${apiServer}/projects/${projectId}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch project records
|
||||||
|
*/
|
||||||
|
async function fetchProjects(jwt: string) {
|
||||||
|
const url = `${apiServer}/projects`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create project
|
||||||
|
*/
|
||||||
|
async function createProject(jwt: string, name: string, detail: string, isPublic: boolean) {
|
||||||
|
const newProjectData = {
|
||||||
|
name,
|
||||||
|
detail,
|
||||||
|
isPublic,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(newProjectData),
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = `${apiServer}/projects`;
|
||||||
|
|
||||||
|
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 creating new project:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update project
|
||||||
|
*/
|
||||||
|
async function updateProject(jwt: string, projectId: number, name: string, detail: string, isPublic: boolean) {
|
||||||
|
const updatedProjectData = {
|
||||||
|
name,
|
||||||
|
detail,
|
||||||
|
isPublic,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchOptions = {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(updatedProjectData),
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = `${apiServer}/projects/${projectId}`;
|
||||||
|
|
||||||
|
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 updating project:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete project
|
||||||
|
*/
|
||||||
|
async function deleteProject(jwt: string, projectId: number) {
|
||||||
|
const fetchOptions = {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = `${apiServer}/projects/${projectId}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, fetchOptions);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error deleting project:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { fetchProject, fetchProjects, createProject, updateProject, deleteProject };
|
||||||
Reference in New Issue
Block a user