Sprint-3/MS-26 #23

Merged
joseph merged 2 commits from Sprint-3/MS-26 into master 2024-09-13 13:26:36 +10:00
5 changed files with 693 additions and 209 deletions

View File

@ -35,6 +35,7 @@ import {
IconSun,
IconTicket,
IconUsersGroup,
IconZoomExclamation,
} from '@tabler/icons-react'
import { useCallback, useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
@ -87,6 +88,12 @@ const data = [
icon: IconListCheck,
group: 'admin',
},
{
link: '/test-report',
label: 'Test Report',
icon: IconZoomExclamation,
group: 'admin',
},
// { link: '/jira', label: 'Jira', icon: IconSubtask },
// { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush },
// { link: '/general-setting', label: 'General Setting', icon: IconSettings },
@ -184,7 +191,8 @@ const Navbar = ({
display={
g.name === 'normal'
? 'block'
: (user?.user?.permission.includes(g.name) || user?.user?.permission.includes('hr'))
: user?.user?.permission.includes(g.name) ||
user?.user?.permission.includes('hr')
? 'block'
: 'none'
}
@ -198,7 +206,9 @@ const Navbar = ({
.filter((i) => {
return (
i.group === g.name &&
((user?.user?.permission.includes('admin') || user?.user?.permission.includes('hr')) || g.name !== 'admin')
(user?.user?.permission.includes('admin') ||
user?.user?.permission.includes('hr') ||
g.name !== 'admin')
)
})
.map((item) => (
@ -301,10 +311,18 @@ const Navbar = ({
const renderQRCode = async () => {
try {
const res = await get(getQRCode+"/"+user?.user?.id)
if(res.status){
window.open(import.meta.env.VITE_BACKEND_URL.includes('localhost')?`${import.meta.env.VITE_BACKEND_URL}storage/qrcode/qrcode_${user?.user?.id}.svg`:
`${import.meta.env.VITE_BACKEND_URL}image/storage/qrcode/qrcode_${user?.user?.id}.svg`, '_blank');
const res = await get(getQRCode + '/' + user?.user?.id)
if (res.status) {
window.open(
import.meta.env.VITE_BACKEND_URL.includes('localhost')
? `${import.meta.env.VITE_BACKEND_URL}storage/qrcode/qrcode_${
user?.user?.id
}.svg`
: `${import.meta.env.VITE_BACKEND_URL}image/storage/qrcode/qrcode_${
user?.user?.id
}.svg`,
'_blank',
)
}
} catch (error) {
console.log(error)
@ -324,7 +342,17 @@ const Navbar = ({
{user.user.name}
</Code>
<Tooltip label="Your QR code" fz={'xs'}>
<IconQrcode onClick={()=>renderQRCode()} color='#fff164' width={28} height={28} style={{border: "solid 2px orange", borderRadius: '5px', cursor:'pointer'}}/>
<IconQrcode
onClick={() => renderQRCode()}
color="#fff164"
width={28}
height={28}
style={{
border: 'solid 2px orange',
borderRadius: '5px',
cursor: 'pointer',
}}
/>
</Tooltip>
</Group>
{links}

View File

@ -1,33 +1,19 @@
import { createOrUpdateUser } from '@/api/Admin'
import { ButtonCopy } from '@/components/CopyClipboard/CopyClipboard'
import { post } from '@/rtk/helpers/apiService'
import { update } from '@/rtk/helpers/CRUD'
import { TUser } from '@/variables/types'
import {
Box,
Button,
Code,
Dialog,
Group,
Modal,
MultiSelect,
Select,
Table,
Text,
TextInput,
Title,
Title
} from '@mantine/core'
import { useForm } from '@mantine/form'
import { notifications } from '@mantine/notifications'
import { useEffect, useState } from 'react'
import classes from './SprintReview.module.css'
const SprintReview = () => {
// const [users, setUsers] = useState<TUser[]>([])
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 [action, setAction] = useState('')
// const [info, setInfo] = useState('')
const [filter, setFilter] = useState({
typeReason: '',
statusFilter: '',
@ -68,14 +54,14 @@ const SprintReview = () => {
// fetchData()
// }, [])
const form = useForm({
initialValues: {
id: 0,
name: '',
email: '',
permission: '',
},
})
// const form = useForm({
// initialValues: {
// id: 0,
// name: '',
// email: '',
// permission: '',
// },
// })
// const getAll = async () => {
// try {
@ -88,36 +74,36 @@ const SprintReview = () => {
// }
// }
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 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 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 {
@ -156,8 +142,13 @@ const SprintReview = () => {
</h3>
<Button
onClick={() => {
setAction('add')
form.reset()
notifications.show({
title: 'Success',
message: 'Intergate api update',
color: 'green',
})
// setAction('add')
// form.reset()
}}
>
Update
@ -343,151 +334,6 @@ const SprintReview = () => {
</Table.Tbody>
</Table>
</Box>
{/* Add/Edit User modal */}
<Modal
opened={action === 'add' || action === 'edit'}
onClose={() => {
setAction('')
form.reset()
}}
title={
<Text pl={'sm'} fw={700} fz={'lg'}>
{action === 'add' ? 'Add User' : 'Update User'}
</Text>
}
>
<form
onSubmit={form.onSubmit(async (values) => {
setDisableBtn(true)
action === 'edit'
? await handleUpdate(values)
: await handleCreate(values)
setDisableBtn(false)
})}
>
<Box pl={'md'} pr={'md'}>
<TextInput
label="Name"
mb={'md'}
value={form.values.name}
error={form.errors.name}
onChange={(e) => form.setFieldValue('name', e.target.value)}
/>
<TextInput
label="Email"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<MultiSelect
label={'Permission(s)'}
required
data={['staff', 'admin', 'hr']}
value={
typeof form.values.permission === 'string'
? form.values.permission
.split(',')
.filter((p) => p.trim() !== '')
: form.values.permission
}
error={form.errors.permisstion}
onChange={(e) =>
form.setFieldValue(
'permission',
e!.filter((p) => p.trim() !== '').join(','),
)
}
/>
<Box ta={'center'}>
{action === 'add' ? (
<Button
mt={'lg'}
bg={'green'}
type="submit"
disabled={disableBtn}
>
Create
</Button>
) : (
<Button
mt={'lg'}
bg={'green'}
type="submit"
disabled={disableBtn}
>
Save
</Button>
)}
</Box>
</Box>
</form>
</Modal>
<Modal
opened={action === 'review'}
onClose={() => {
setAction('')
}}
size={'lg'}
title={
<Text pl={'sm'} fw={700} fz={'lg'}>
Information to get started
</Text>
}
>
<Text c={'red'} fz={'sm'} fw={700}>
!! Important note: Please remind user to change password after logging
in !!
</Text>
<Code block>
<Box ta={'right'}>
<ButtonCopy value={info} />
</Box>
{info}
</Code>
</Modal>
<Dialog
className={classes.dialog}
opened={action === 'delete'}
withCloseButton
onClose={() => setAction('')}
size="lg"
radius="md"
position={{ top: 30, right: 10 }}
>
<Text className={classes.dialogText} size="sm" mb="xs" fw={500}>
Do you want to delete this user?
<Group justify="center" m={10}>
<Button
disabled={activeBtn}
fw={700}
size="xs"
variant="light"
onClick={async () => {
setActiveBtn(true)
// await handleDelete(item.id)
setActiveBtn(false)
setAction('')
}}
>
Yes
</Button>
<Button
fw={700}
size="xs"
variant="light"
onClick={() => setAction('')}
>
Cancel
</Button>
</Group>
</Text>
</Dialog>
</div>
)
}

View File

@ -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);
}

View File

@ -0,0 +1,548 @@
import { TUser } from '@/variables/types'
import {
Box,
Button,
Dialog,
Group,
Modal,
Select,
Table,
Text,
Textarea,
TextInput,
Title,
} from '@mantine/core'
import { useForm } from '@mantine/form'
import { IconInfoSquare, IconSquareXFilled } from '@tabler/icons-react'
import { useEffect, useState } from 'react'
import classes from './TestReport.module.css'
const TestReport = () => {
// const [users, setUsers] = useState<TUser[]>([])
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<DataTimeType[]>([])
// const [dataReason, setDataReason] = useState<DataReason[]>([])
// 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
console.log(data, 'data')
// 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 handleDelete = async (id: number) => {
try {
const data = {
id: id,
}
console.log(data, 'data')
// await Xdelete(
// deleteUser,
// { id: id },
// // , getAll
// )
} catch (error) {
console.log(error)
}
}
useEffect(() => {
// getAll()
}, [])
const rowStyle = {
height: '30px', // Điều chỉnh chiều cao hàng
}
return (
<div>
<div className={classes.title}>
<h3>
<Text>Admin/</Text>
Test Report
</h3>
<Button
onClick={() => {
setAction('add')
form.reset()
}}
>
+ Add
</Button>
</div>
<Box display={'flex'} p={15}>
<Box
style={{
display: 'flex',
flexFlow: 'column',
}}
w={'40%'}
>
<Box w="100%" display={'flex'}>
<Text
mr={'xs'}
style={{ alignContent: 'center' }}
fw={600}
size={'md'}
>
Project:
</Text>
<Select
clearable
w="50%"
value={filter.typeReason}
size="xs"
label=""
data={[]}
onChange={(e) => {
setFilter({ ...filter, typeReason: e! })
}}
></Select>
<Text
ml={'lg'}
style={{ alignContent: 'center' }}
fw={600}
size={'md'}
>
Sprint:
</Text>
<Select
clearable
w="50%"
value={filter.statusFilter}
size="xs"
ml={'sm'}
label=""
data={
[
// { value: 'WAITING', label: 'WAITING' },
// { value: 'CONFIRMED', label: 'CONFIRMED' },
// { value: 'REFUSED', label: 'REFUSED' },
]
}
onChange={(e) => {
setFilter({ ...filter, statusFilter: e! })
}}
></Select>
</Box>
</Box>
<Box
style={{
display: 'flex',
flexFlow: 'column',
// backgroundColor: 'yellow',
alignSelf: 'center',
}}
w={'60%'}
>
<Text style={{ textAlign: 'center' }} fw={600}>
Project Name/ Sprint Name
</Text>
</Box>
</Box>
<Box>
{/* Tiêu đề Criteria for Sprint */}
<Title order={5} ml="xs">
List bug:
</Title>
{/* Bảng Criteria for Sprint */}
<Table
striped
highlightOnHover
withTableBorder
withColumnBorders
// me={'xs'}
>
<Table.Thead>
<Table.Tr bg="#228be66b" style={rowStyle}>
<Table.Th style={{ textAlign: 'center', width: '150px' }}>
Test case
</Table.Th>
<Table.Th style={{ textAlign: 'center', width: '150px' }}>
Position
</Table.Th>
<Table.Th style={{ textAlign: 'center', width: '150px' }}>
Input
</Table.Th>
<Table.Th style={{ textAlign: 'center', width: '150px' }}>
Note
</Table.Th>
<Table.Th style={{ textAlign: 'center', width: '150px' }}>
Expect output
</Table.Th>
<Table.Th style={{ textAlign: 'center', width: '150px' }}>
Actual output
</Table.Th>
<Table.Th style={{ textAlign: 'center' }}>Note</Table.Th>
<Table.Th style={{ textAlign: 'center', width: '100px' }}>
Action
</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{[...Array(5)].map((_, index) => (
<Table.Tr key={index} style={rowStyle}>
<Table.Td>string</Table.Td>
<Table.Td>string</Table.Td>
<Table.Td>string</Table.Td>
<Table.Td>string</Table.Td>
<Table.Td>string</Table.Td>
<Table.Td>string</Table.Td>
<Table.Td>string</Table.Td>
<Table.Td>
<Box className={classes.optionIcon}>
<IconInfoSquare
className={classes.editIcon}
onClick={() => {
setAction('review')
// setItem(row)
form.reset()
}}
width={20}
height={20}
/>
<IconSquareXFilled
className={classes.deleteIcon}
onClick={() => {
setAction('delete')
// setItem(row)
form.reset()
}}
width={20}
height={20}
/>
</Box>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Box>
{/* Add Test modal */}
<Modal
opened={action === 'add'}
onClose={() => {
setAction('')
form.reset()
}}
title={
<Text pl={'sm'} fw={700} fz={'lg'}>
{action === 'add' ? 'Add Test' : ''}
</Text>
}
>
<form
onSubmit={form.onSubmit(async (values) => {
setDisableBtn(true)
await handleCreate(values)
setDisableBtn(false)
})}
>
<Box pl={'md'} pr={'md'}>
<TextInput
required
label="Name"
mb={'md'}
value={form.values.name}
error={form.errors.name}
onChange={(e) => form.setFieldValue('name', e.target.value)}
/>
<TextInput
required
label="Position"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<TextInput
required
label="Input"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<Box display={'flex'}>
<Box style={{ display: 'flex', flexFlow: 'column' }} w={'45%'}>
<TextInput
label="Expect Output"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<TextInput
label="Issue ID On Jira"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
</Box>
<Box
style={{ display: 'flex', flexFlow: 'column' }}
w={'10%'}
></Box>
<Box style={{ display: 'flex', flexFlow: 'column' }} w={'45%'}>
<TextInput
label="Actual Output"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<TextInput
label="Bug ID On Jira"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
</Box>
</Box>
<Textarea
label="Note"
required
value={form.values.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<Box ta={'center'}>
<Button
mt={'lg'}
bg={'green'}
type="submit"
disabled={disableBtn}
>
Create
</Button>
</Box>
</Box>
</form>
</Modal>
<Modal
opened={action === 'review'}
onClose={() => {
setAction('')
}}
size={'lg'}
title={
<Text pl={'sm'} fw={700} fz={'lg'}>
Detail Test Report
</Text>
}
>
<Box pl={'md'} pr={'md'}>
<TextInput
label="Name"
readOnly
style={{ pointerEvents: 'none' }}
mb={'md'}
value={'!23123'}
error={form.errors.name}
onChange={(e) => form.setFieldValue('name', e.target.value)}
/>
<TextInput
label="Position"
readOnly
style={{ pointerEvents: 'none' }}
mb={'md'}
value={'12312231'}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<TextInput
readOnly
style={{ pointerEvents: 'none' }}
label="Input"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<Box display={'flex'}>
<Box style={{ display: 'flex', flexFlow: 'column' }} w={'40%'}>
<TextInput
readOnly
style={{ pointerEvents: 'none' }}
label="Expect Output"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<TextInput
readOnly
style={{ pointerEvents: 'none' }}
label="Actual Output"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
</Box>
<Box
style={{ display: 'flex', flexFlow: 'column' }}
w={'20%'}
></Box>
<Box style={{ display: 'flex', flexFlow: 'column' }} w={'40%'}>
<TextInput
readOnly
style={{ pointerEvents: 'none' }}
label="Issue ID On Jira"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<TextInput
readOnly
style={{ pointerEvents: 'none' }}
label="Bug ID On Jira"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
</Box>
</Box>
<Textarea
readOnly
style={{ pointerEvents: 'none' }}
label="Note"
value={form.values.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
<TextInput
readOnly
style={{ pointerEvents: 'none', marginTop: '10px' }}
label="Create By"
mb={'md'}
value={form.values.email}
error={form.errors.email}
onChange={(e) => form.setFieldValue('email', e.target.value)}
/>
</Box>
</Modal>
<Dialog
className={classes.dialog}
opened={action === 'delete'}
withCloseButton
onClose={() => setAction('')}
size="lg"
radius="md"
position={{ top: 30, right: 10 }}
>
<Text className={classes.dialogText} size="sm" mb="xs" fw={500}>
Do you want to delete this record?
<Group justify="center" m={10}>
<Button
disabled={activeBtn}
fw={700}
size="xs"
variant="light"
onClick={async () => {
setActiveBtn(true)
await handleDelete(2)
setActiveBtn(false)
setAction('')
}}
>
Yes
</Button>
<Button
fw={700}
size="xs"
variant="light"
onClick={() => setAction('')}
>
Cancel
</Button>
</Group>
</Text>
</Dialog>
</div>
)
}
export default TestReport

View File

@ -6,6 +6,7 @@ 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 TestReport from '@/pages/TestReport/TestReport'
import Tickets from '@/pages/Tickets/Tickets'
import TicketsManagement from '@/pages/TicketsManagement/TicketsManagement'
import Timekeeping from '@/pages/Timekeeping/Timekeeping'
@ -159,6 +160,20 @@ const mainRoutes = [
</ProtectedRoute>
),
},
{
path: '/test-report',
element: (
<ProtectedRoute mode="route" permission="admin,hr">
<BasePage
main={
<>
<TestReport />
</>
}
></BasePage>
</ProtectedRoute>
),
},
// {
// path: '/packages',
// element: (