diff --git a/BACKEND/database/migrations/2024_09_12_083532_create_sprints_criterias_table.php b/BACKEND/database/migrations/2024_09_12_083532_create_sprints_criterias_table.php index 7a4bd70..fab0f99 100644 --- a/BACKEND/database/migrations/2024_09_12_083532_create_sprints_criterias_table.php +++ b/BACKEND/database/migrations/2024_09_12_083532_create_sprints_criterias_table.php @@ -20,7 +20,7 @@ return new class extends Migration $table->string('note')->nullable(); $table->string('created_by'); - $table->foreign('sprint_id')->references('id')->on('sprints')->onDelete('cascade'); + $table->foreign('sprint_id')->references('id')->on('sprint')->onDelete('cascade'); $table->foreign('criteria_id')->references('id')->on('criterias')->onDelete('cascade'); }); } diff --git a/BACKEND/database/migrations/2024_09_12_083955_create_test_cases_for_sprint_table.php b/BACKEND/database/migrations/2024_09_12_083955_create_test_cases_for_sprint_table.php index 9b8eeff..08eb19e 100644 --- a/BACKEND/database/migrations/2024_09_12_083955_create_test_cases_for_sprint_table.php +++ b/BACKEND/database/migrations/2024_09_12_083955_create_test_cases_for_sprint_table.php @@ -24,7 +24,7 @@ return new class extends Migration $table->unsignedBigInteger('sprint_id'); $table->string('created_by'); - $table->foreign('sprint_id')->references('id')->on('sprints')->onDelete('cascade'); + $table->foreign('sprint_id')->references('id')->on('sprint')->onDelete('cascade'); }); } diff --git a/BACKEND/database/migrations/2024_09_12_084137_create_users_criterias_table.php b/BACKEND/database/migrations/2024_09_12_084137_create_users_criterias_table.php index 92dacfc..53d276a 100644 --- a/BACKEND/database/migrations/2024_09_12_084137_create_users_criterias_table.php +++ b/BACKEND/database/migrations/2024_09_12_084137_create_users_criterias_table.php @@ -21,7 +21,7 @@ return new class extends Migration $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('criteria_id')->references('id')->on('criterias')->onDelete('cascade'); - $table->foreign('sprint_id')->references('id')->on('sprints')->onDelete('cascade'); + $table->foreign('sprint_id')->references('id')->on('sprint')->onDelete('cascade'); }); } diff --git a/FRONTEND/src/components/Navbar/Navbar.tsx b/FRONTEND/src/components/Navbar/Navbar.tsx index de8a7f9..d7ccc81 100755 --- a/FRONTEND/src/components/Navbar/Navbar.tsx +++ b/FRONTEND/src/components/Navbar/Navbar.tsx @@ -40,6 +40,7 @@ import { useCallback, useEffect, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' import PasswordRequirementInput from '../PasswordRequirementInput/PasswordRequirementInput' import classes from './NavbarSimpleColored.module.css' +import { IconListCheck } from '@tabler/icons-react' const data = [ // { link: '/dashboard', label: 'Dashboard', icon: IconHome }, @@ -79,7 +80,13 @@ const data = [ label: 'Users Management', icon: IconUsersGroup, group: 'admin', - } + }, + { + link: '/sprint-review', + label: 'Sprint Review', + icon: IconListCheck, + group: 'admin', + }, // { link: '/jira', label: 'Jira', icon: IconSubtask }, // { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush }, // { link: '/general-setting', label: 'General Setting', icon: IconSettings }, diff --git a/FRONTEND/src/pages/SprintReview/SprintReview.module.css b/FRONTEND/src/pages/SprintReview/SprintReview.module.css new file mode 100644 index 0000000..6b56ab4 --- /dev/null +++ b/FRONTEND/src/pages/SprintReview/SprintReview.module.css @@ -0,0 +1,47 @@ +.title { + background-color: light-dark(var(white), var(--mantine-color-dark-7)); + z-index: 100; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--mantine-spacing-sm) var(--mantine-spacing-lg) + var(--mantine-spacing-sm); + border-bottom: solid rgba(201, 201, 201, 0.377) 1px; +} + +.optionIcon { + display: flex; + justify-content: space-evenly; +} + +.deleteIcon { + color: red; + cursor: pointer; + padding: 2px; + border-radius: 25%; +} + +.editIcon { + color: rgb(9, 132, 132); + cursor: pointer; + padding: 2px; + border-radius: 25%; +} + +.editIcon:hover { + background-color: rgba(203, 203, 203, 0.809); +} + +.deleteIcon:hover { + background-color: rgba(203, 203, 203, 0.809); +} + +.dialog { + background-color: light-dark(white, #2d353c); + text-align: center; + border: solid 1px rgb(255, 145, 0); +} + +.dialogText { + color: light-dark(#2d353c, white); +} diff --git a/FRONTEND/src/pages/SprintReview/SprintReview.tsx b/FRONTEND/src/pages/SprintReview/SprintReview.tsx new file mode 100644 index 0000000..5c75285 --- /dev/null +++ b/FRONTEND/src/pages/SprintReview/SprintReview.tsx @@ -0,0 +1,495 @@ +import { createOrUpdateUser, deleteUser } from '@/api/Admin' +import { ButtonCopy } from '@/components/CopyClipboard/CopyClipboard' +import { post } from '@/rtk/helpers/apiService' +import { update, Xdelete } from '@/rtk/helpers/CRUD' +import { TUser } from '@/variables/types' +import { + Box, + Button, + Code, + Dialog, + Group, + Modal, + MultiSelect, + Select, + Table, + Text, + TextInput, + Title, +} from '@mantine/core' +import { useForm } from '@mantine/form' +import { useEffect, useState } from 'react' +import classes from './SprintReview.module.css' + +const SprintReview = () => { + // const [users, setUsers] = useState([]) + const [action, setAction] = useState('') + const [activeBtn, setActiveBtn] = useState(false) + // const [item, setItem] = useState({ id: 0 }) + const [disableBtn, setDisableBtn] = useState(false) + const [info, setInfo] = useState('') + const [filter, setFilter] = useState({ + typeReason: '', + statusFilter: '', + }) + // const [dataTimeType, setDataTimeType] = useState([]) + // const [dataReason, setDataReason] = useState([]) + + // const getListMasterByType = async (type: string) => { + // try { + // const params = { + // type: type, + // } + // const res = await get(getListMaster, params) + // if (res.status) { + // return res.data + // } + // } catch (error: any) { + // notifications.show({ + // title: 'Error', + // message: error.message ?? error, + // color: 'red', + // }) + // } + // return [] + // } + + // useEffect(() => { + // const fetchData = async () => { + // const resultTimeType = await getListMasterByType('TIME_TYPE') + // setDataTimeType( + // resultTimeType.filter((item: DataTimeType) => item.c_code !== 'ALL'), + // ) + + // const resultReason = await getListMasterByType('REASON') + // setDataReason(resultReason) + // } + + // fetchData() + // }, []) + + const form = useForm({ + initialValues: { + id: 0, + name: '', + email: '', + permission: '', + }, + }) + + // const getAll = async () => { + // try { + // const res = await get(getAllUsers) + // if (res.status) { + // setUsers(res.data) + // } + // } catch (error) { + // console.log(error) + // } + // } + + const handleCreate = async (values: TUser) => { + try { + const { id, ...data } = values + const res = await post(createOrUpdateUser, data) + if (res.status === true) { + setAction('review') + form.reset() + // getAll() + setInfo(JSON.stringify(res.data, null, 2)) + } + } catch (error) { + console.log(error) + } + } + + const handleUpdate = async (values: TUser) => { + try { + const res = await update( + createOrUpdateUser, + values, + // , getAll + ) + if (res === true) { + setAction('') + form.reset() + } + } catch (error) { + console.log(error) + } + } + + const handleDelete = async (id: number) => { + try { + await Xdelete( + deleteUser, + { id: id }, + // , getAll + ) + } catch (error) { + console.log(error) + } + } + + useEffect(() => { + // getAll() + }, []) + + const pointsOptions = [ + { value: '1', label: '1' }, + { value: '2', label: '2' }, + { value: '3', label: '3' }, + { value: '4', label: '4' }, + { value: '5', label: '5' }, + ] + + const rowStyle = { + height: '30px', // Điều chỉnh chiều cao hàng + } + + return ( +
+
+

+ Admin/ + Sprint Review +

+ +
+ + + + + + Project: + + + + Sprint: + + + + + + + Project Name/ Sprint Name + + + + + + {/* Tiêu đề Criteria for Sprint */} + + Criteria for Sprint: + + + {/* Bảng Criteria for Sprint */} + + + + + Criteria + + + Expect Result + + + Actual Result + + Note + + Point + + + + + {[...Array(5)].map((_, index) => ( + + string + string + string + + string (set default value when select point) + + + + + + + +
+ + {/* Tiêu đề Criteria for Users */} + + Criteria for Users: + + + {/* Criteria for Users Table */} + + + + + User + + + Criteria + + Note + + Created by + + + Point + + + + + {[...Array(5)].map((_, index) => ( + + string + string + + string (set default value when select point) + + string + +
+
+ + {/* Add/Edit User modal */} + { + setAction('') + form.reset() + }} + title={ + + {action === 'add' ? 'Add User' : 'Update User'} + + } + > +
{ + setDisableBtn(true) + action === 'edit' + ? await handleUpdate(values) + : await handleCreate(values) + setDisableBtn(false) + })} + > + + form.setFieldValue('name', e.target.value)} + /> + + form.setFieldValue('email', e.target.value)} + /> + + p.trim() !== '') + : form.values.permission + } + error={form.errors.permisstion} + onChange={(e) => + form.setFieldValue( + 'permission', + e!.filter((p) => p.trim() !== '').join(','), + ) + } + /> + + {action === 'add' ? ( + + ) : ( + + )} + + +
+
+ + { + setAction('') + }} + size={'lg'} + title={ + + Information to get started + + } + > + + !! Important note: Please remind user to change password after logging + in !! + + + + + + {info} + + + + setAction('')} + size="lg" + radius="md" + position={{ top: 30, right: 10 }} + > + + Do you want to delete this user? + + + + + + +
+ ) +} + +export default SprintReview diff --git a/FRONTEND/src/routes/main.tsx b/FRONTEND/src/routes/main.tsx index b447049..92653e5 100755 --- a/FRONTEND/src/routes/main.tsx +++ b/FRONTEND/src/routes/main.tsx @@ -5,6 +5,7 @@ import ProtectedRoute from '@/components/ProtectedRoute/ProtectedRoute' import PageLogin from '@/pages/Auth/Login/Login' import LeaveManagement from '@/pages/LeaveManagement/LeaveManagement' import PageNotFound from '@/pages/NotFound/NotFound' +import SprintReview from '@/pages/SprintReview/SprintReview' import Tickets from '@/pages/Tickets/Tickets' import TicketsManagement from '@/pages/TicketsManagement/TicketsManagement' import Timekeeping from '@/pages/Timekeeping/Timekeeping' @@ -137,7 +138,21 @@ const mainRoutes = [ - + + + } + > + + ), + }, + { + path: '/sprint-review', + element: ( + + + } > diff --git a/FRONTEND/src/variables/types.ts b/FRONTEND/src/variables/types.ts index 1a0a29e..ad06964 100755 --- a/FRONTEND/src/variables/types.ts +++ b/FRONTEND/src/variables/types.ts @@ -1,79 +1,91 @@ export type TGeneralSettingValues = { - page_title: string - email: string - phone: string - meta_title: string - meta_description: string - meta_keyword: string - facebook: string - twitter: string - linkedin: string - favicon: File | null | string - logo: File | null | string - address: string - license: string - } + page_title: string + email: string + phone: string + meta_title: string + meta_description: string + meta_keyword: string + facebook: string + twitter: string + linkedin: string + favicon: File | null | string + logo: File | null | string + address: string + license: string +} export type TPackageValues = { - id?: number - title: string - description: string - point: number | string - price: number | string - status?: boolean - } + id?: number + title: string + description: string + point: number | string + price: number | string + status?: boolean +} export type TDiscountValues = { - id?: number - code: string - value: string - active_date: any | null - expiry: any | null - date_used: any | null - status?: boolean - discount_type: string - } + id?: number + code: string + value: string + active_date: any | null + expiry: any | null + date_used: any | null + status?: boolean + discount_type: string +} export type TClientValues = { - email: string - name: string - phone: string - company: string - country_code: string - password: string - confirm_password: string - status: boolean - point: string - } + email: string + name: string + phone: string + company: string + country_code: string + password: string + confirm_password: string + status: boolean + point: string +} export type TOrderValues = { - id?: number - payment_id: string - point: string - total_price: string - user_id: string - package_id: string - discount: string - status: string - created_at?: string - updated_at?: string - user_name?: string - avatar?: string - user_email?: string - user_phone?: string - user_company?: string - user_country?: string - country_code?: string - discount_value?: string - discount_type?: string - package_name?: string - package_price?: string - discounted_package_price?: string - } + id?: number + payment_id: string + point: string + total_price: string + user_id: string + package_id: string + discount: string + status: string + created_at?: string + updated_at?: string + user_name?: string + avatar?: string + user_email?: string + user_phone?: string + user_company?: string + user_country?: string + country_code?: string + discount_value?: string + discount_type?: string + package_name?: string + package_price?: string + discounted_package_price?: string +} - export type TUser = { - id: number; - email: string; - name: string; - permission: string - } \ No newline at end of file +export type TUser = { + id: number + email: string + name: string + permission: string +} + +export type DataReason = { + id: number + c_code: string + c_name: string +} + +export type DataTimeType = { + id: number + c_code: string + c_name: string +}