create function user manament
This commit is contained in:
parent
8f6092be62
commit
bb3c9b2106
|
|
@ -3,16 +3,108 @@
|
|||
namespace Modules\Auth\app\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Traits\IsAPI;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Modules\Auth\app\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
use IsAPI;
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('jwt.auth');
|
||||
}
|
||||
public function users()
|
||||
{
|
||||
return response()->json(User::all());
|
||||
return response()->json(['data' => User::all(), 'status' => true]);
|
||||
}
|
||||
|
||||
public function createOrUpdate(Request $request)
|
||||
{
|
||||
|
||||
$this->apiValidation($request, [
|
||||
'name' => 'required|string',
|
||||
'email' => 'required|email',
|
||||
'permission' => 'required|string',
|
||||
]);
|
||||
|
||||
if ($request->has('id')) {
|
||||
$payload = $request->only(['name', 'email', 'permission']);
|
||||
$user = User::find($request->id);
|
||||
|
||||
$user->update($payload);
|
||||
return response()->json(['data' => $user, 'status' => true, 'message' => 'Update successful']);
|
||||
} else {
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => bcrypt('Work@1234'),
|
||||
'permission' => $request->permission
|
||||
]);
|
||||
|
||||
$user_res = [
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'password' => 'Work@1234',
|
||||
'url' => 'https://ms.prology.net'
|
||||
];
|
||||
|
||||
$gitea = Http::withHeaders([
|
||||
'Authorization' => 'token ' . env('GITEA_ADMIN_TOKEN'),
|
||||
'Accept' => 'application/json',
|
||||
])->post('https://gitea.nswteam.net/api/v1/admin/users', [
|
||||
'email' => $request->email,
|
||||
'full_name' => $request->name,
|
||||
'login_name' => Str::of($request->name)->lower()->replace(' ', ''),
|
||||
'password' => 'Work@1234',
|
||||
'must_change_password' => false,
|
||||
'send_notify' => false,
|
||||
'username' => Str::of($request->name)->lower()->replace(' ', '')
|
||||
]);
|
||||
|
||||
$gitea_data = $gitea->json();
|
||||
|
||||
$gitea_res = [
|
||||
"login" => $gitea_data['login'],
|
||||
"full_name" => $gitea_data['full_name'],
|
||||
"email" => $gitea_data['email'],
|
||||
"password" => 'Work@1234',
|
||||
"url" => 'https://gitea.nswteam.net',
|
||||
];
|
||||
|
||||
$adminEmail = env('ZULIP_ADMIN_EMAIL');
|
||||
$apiKey = env('ZULIP_API_KEY');
|
||||
$apiUrl = env('ZULIP_API_URL') . '/invites';
|
||||
|
||||
$zulip = Http::asForm()->withBasicAuth($adminEmail, $apiKey)->post($apiUrl, [
|
||||
'invitee_emails' => $request->email,
|
||||
'invite_expires_in_minutes' => 1440,
|
||||
'invite_as' => 400,
|
||||
'stream_ids' => '[22]'
|
||||
]);
|
||||
|
||||
$zulip_data = $zulip->json();
|
||||
$zulip_data['msg'] = 'Check inbox email ' . $request->email;
|
||||
$zulip_data['url'] = 'https://zulip.ipsupply.com.au';
|
||||
|
||||
return response()->json(['data' => ['user' => $user_res, 'gitea' => $gitea_res, 'zulip' => $zulip_data], 'status' => true, 'message' => 'Create successful']);
|
||||
}
|
||||
|
||||
return response()->json(['status' => false, 'message' => 'Process fail']);
|
||||
}
|
||||
|
||||
public function delete(Request $request)
|
||||
{
|
||||
$user = User::find($request->id);
|
||||
|
||||
if ($user) {
|
||||
$user->delete();
|
||||
return response()->json(['status' => true, 'message' => 'Delete successful']);
|
||||
}
|
||||
|
||||
return response()->json(['status' => false, 'message' => 'User not found']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ use Tymon\JWTAuth\Facades\JWTAuth;
|
|||
class User extends Authenticatable implements JWTSubject
|
||||
{
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
use HasRoles;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
|
@ -26,6 +25,7 @@ class User extends Authenticatable implements JWTSubject
|
|||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'permission'
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ Route::middleware(['api'])
|
|||
->prefix('v1')
|
||||
->name('api.v1.')
|
||||
->group(function () {
|
||||
Route::get('users', [UserController::class, 'users']);
|
||||
Route::middleware(['jwt.auth'])->get('users', [UserController::class, 'users']);
|
||||
Route::middleware(['jwt.auth'])->post('users/createOrUpdate', [UserController::class, 'createOrUpdate']);
|
||||
Route::middleware(['jwt.auth'])->get('users/delete', [UserController::class, 'delete']);
|
||||
|
||||
Route::post('login', [LoginController::class, 'login']);
|
||||
Route::middleware(['jwt.auth'])->post('logout', [LoginController::class, 'logout']);
|
||||
Route::middleware(['jwt.auth'])->post('change-password', [AuthController::class, 'updatePassword']);
|
||||
|
|
|
|||
|
|
@ -1,55 +1,6 @@
|
|||
const API_URL = import.meta.env.VITE_BACKEND_URL + 'api/'
|
||||
|
||||
export const getDetailUser = API_URL + 'v1/admin/detail'
|
||||
// General setting
|
||||
export const updateGeneralSetting = API_URL + 'v1/admin/setting'
|
||||
export const getGeneralSetting = API_URL + 'v1/admin/setting'
|
||||
export const clearCacheAPI = API_URL + 'v1/admin/setting/clear-cache'
|
||||
|
||||
// Packages
|
||||
export const createPackage = API_URL + 'v1/admin/package/create'
|
||||
export const getAllPackage = API_URL + 'v1/admin/package/all'
|
||||
export const updatePackage = API_URL + 'v1/admin/package/update'
|
||||
export const deletePackage = API_URL + 'v1/admin/package/delete'
|
||||
|
||||
// Custom theme setting
|
||||
export const getCustomTheme = API_URL + 'v1/admin/custom-theme'
|
||||
export const updateCustomTheme = API_URL + 'v1/admin/custom-theme'
|
||||
|
||||
// Discounts
|
||||
export const getDiscounts = API_URL + 'v1/admin/discount/get'
|
||||
export const getDiscountType = API_URL + 'v1/admin/discount-type/all'
|
||||
export const createDiscount = API_URL + 'v1/admin/discount/create'
|
||||
export const updateDiscount = API_URL + 'v1/admin/discount/update'
|
||||
export const deleteDiscount = API_URL + 'v1/admin/discount/delete'
|
||||
|
||||
// Client
|
||||
export const getClients = API_URL + 'v1/admin/client/get'
|
||||
export const getCountry = API_URL + 'v1/admin/h-country'
|
||||
export const addUser = API_URL + 'v1/admin/client/create'
|
||||
export const updateUser = API_URL + 'v1/admin/client/update'
|
||||
|
||||
// Banners
|
||||
export const getBanners = API_URL + 'v1/admin/banner/all'
|
||||
export const createBanner = API_URL + 'v1/admin/banner/create'
|
||||
export const updateBanner = API_URL + 'v1/admin/banner/update'
|
||||
export const deleteBanner = API_URL + 'v1/admin/banner/delete'
|
||||
|
||||
// Orders
|
||||
export const getOrders = API_URL + 'v1/admin/order/get'
|
||||
export const updateOrder = API_URL + 'v1/admin/order/update'
|
||||
|
||||
// CheckHistory
|
||||
export const getCheckHistory = API_URL + 'v1/admin/sn-check-history/get'
|
||||
export const getDetailCheckById = API_URL + 'v1/admin/sn-check-history/show-detail'
|
||||
|
||||
// Contacts
|
||||
export const getContacts = API_URL + 'v1/admin/contact/get'
|
||||
|
||||
// Dashboard
|
||||
export const getDashboard = API_URL + 'v1/admin/dashboard/get'
|
||||
export const statisticSearchSNByMonth = API_URL + 'v1/admin/dashboard/statistics-search-sn-by-month'
|
||||
export const statisticRevenuesByMonth = API_URL + 'v1/admin/dashboard/statistics-revenues-by-month'
|
||||
|
||||
// Tracking
|
||||
export const getListTracking = API_URL + 'v1/admin/tracking'
|
||||
|
|
@ -84,4 +35,10 @@ export const getTickets = API_URL + 'v1/admin/ticket/all'
|
|||
export const getTicketsOfUser = API_URL + 'v1/admin/ticket/getByUserId'
|
||||
export const deleteTicket = API_URL + 'v1/admin/ticket/delete'
|
||||
export const addTicket = API_URL + 'v1/admin/ticket/create'
|
||||
export const handleTicket = API_URL + 'v1/admin/ticket/handle-ticket'
|
||||
export const handleTicket = API_URL + 'v1/admin/ticket/handle-ticket'
|
||||
|
||||
//Users
|
||||
|
||||
export const getAllUsers = API_URL + 'v1/users'
|
||||
export const createOrUpdateUser = API_URL + 'v1/users/createOrUpdate'
|
||||
export const deleteUser = API_URL + 'v1/users/delete'
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { rem, Tooltip } from '@mantine/core'
|
||||
import { useClipboard } from '@mantine/hooks'
|
||||
import { IconCheck, IconCopy } from '@tabler/icons-react'
|
||||
|
||||
export function ButtonCopy({
|
||||
value,
|
||||
width = 20,
|
||||
height = 20,
|
||||
}: {
|
||||
value: string
|
||||
width?: number
|
||||
height?: number
|
||||
}) {
|
||||
const clipboard = useClipboard()
|
||||
return (
|
||||
<Tooltip
|
||||
label="Link copied!"
|
||||
offset={5}
|
||||
position="bottom"
|
||||
radius="xl"
|
||||
transitionProps={{ duration: 100, transition: 'slide-down' }}
|
||||
opened={clipboard.copied}
|
||||
>
|
||||
{clipboard.copied ? (
|
||||
<IconCheck
|
||||
style={{ width: rem(width), height: rem(height) }}
|
||||
stroke={1.5}
|
||||
/>
|
||||
) : (
|
||||
<IconCopy
|
||||
onClick={() => clipboard.copy(value)}
|
||||
style={{
|
||||
width: rem(width),
|
||||
height: rem(height),
|
||||
color: 'skyblue',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
stroke={1.5}
|
||||
/>
|
||||
)}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ import {
|
|||
IconSun,
|
||||
IconTicket,
|
||||
IconDevices,
|
||||
IconUsersGroup,
|
||||
} from '@tabler/icons-react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
|
@ -70,6 +71,12 @@ const data = [
|
|||
icon: IconDevices,
|
||||
group: 'admin',
|
||||
},
|
||||
{
|
||||
link: '/users',
|
||||
label: 'Users Management',
|
||||
icon: IconUsersGroup,
|
||||
group: 'admin',
|
||||
}
|
||||
// { link: '/jira', label: 'Jira', icon: IconSubtask },
|
||||
// { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush },
|
||||
// { link: '/general-setting', label: 'General Setting', icon: IconSettings },
|
||||
|
|
|
|||
|
|
@ -1,470 +0,0 @@
|
|||
import {
|
||||
createBanner,
|
||||
deleteBanner,
|
||||
getBanners,
|
||||
updateBanner,
|
||||
} from '@/api/Admin'
|
||||
import DataTableAll from '@/components/DataTable/DataTable'
|
||||
import { Xdelete, create, update } from '@/rtk/helpers/CRUD'
|
||||
import { get } from '@/rtk/helpers/apiService'
|
||||
import { getAccessToken } from '@/rtk/localStorage'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
FileInput,
|
||||
Group,
|
||||
HoverCard,
|
||||
Image,
|
||||
Loader,
|
||||
Modal,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
Badge,
|
||||
} from '@mantine/core'
|
||||
import { useForm } from '@mantine/form'
|
||||
import { IconEdit, IconPointFilled, IconTrash } from '@tabler/icons-react'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import classes from './Banner.module.css'
|
||||
|
||||
const Banner = () => {
|
||||
// type TFile = {
|
||||
// name: string
|
||||
// size: number
|
||||
// type: string
|
||||
// }
|
||||
|
||||
type TBanner = {
|
||||
id?: number
|
||||
title: string
|
||||
link: string
|
||||
image?: null | string | File
|
||||
is_active: boolean | string | number
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
const [disableBtn, setDisableBtn] = useState(false)
|
||||
const [action, setAction] = useState('')
|
||||
const [loader, setLoader] = useState(true)
|
||||
const [data, setData] = useState<TBanner[]>([])
|
||||
const [imageTemp, setImageTemp] = useState<string | null | File>('')
|
||||
const columns = [
|
||||
{
|
||||
name: 'id',
|
||||
size: '5%',
|
||||
header: 'ID',
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
size: '15%',
|
||||
header: 'Title',
|
||||
},
|
||||
{
|
||||
name: 'link',
|
||||
size: '20%',
|
||||
header: 'Link',
|
||||
render: (row: TBanner) =>
|
||||
(
|
||||
<Tooltip label={row?.link} position="bottom">
|
||||
<Box style={{ maxWidth: '300px', overflow: 'hidden' }}>
|
||||
{row?.link}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
) ?? <Badge color="blue">Null</Badge>,
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
size: '20%',
|
||||
header: 'Image',
|
||||
render: (row: TBanner) => {
|
||||
return (
|
||||
<HoverCard shadow="md" openDelay={500} position="left">
|
||||
<HoverCard.Target>
|
||||
<Image h={'20vh'} src={row.image} />
|
||||
</HoverCard.Target>
|
||||
<HoverCard.Dropdown>
|
||||
<Image
|
||||
radius="sm"
|
||||
h={300}
|
||||
w="auto"
|
||||
fit="contain"
|
||||
src={row.image}
|
||||
/>
|
||||
</HoverCard.Dropdown>
|
||||
</HoverCard>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'is_active',
|
||||
size: '5%',
|
||||
header: 'Status',
|
||||
render: (row: TBanner) => {
|
||||
return (
|
||||
<Box ta={'center'}>
|
||||
{row.is_active === 1 ? (
|
||||
<IconPointFilled style={{ color: '#19d105' }} />
|
||||
) : (
|
||||
<IconPointFilled style={{ color: 'red' }} />
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'updated_at',
|
||||
size: '10%',
|
||||
header: 'Updated at',
|
||||
render: (row: TBanner) => {
|
||||
return moment(row.updated_at).format('YYYY/MM/DD - HH:mm:ss')
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '#',
|
||||
size: '5%',
|
||||
header: 'Action',
|
||||
render: (row: TBanner) => {
|
||||
return (
|
||||
<Box className={classes.optionIcon}>
|
||||
<Tooltip label="Edit">
|
||||
<IconEdit
|
||||
className={classes.editIcon}
|
||||
width={20}
|
||||
height={20}
|
||||
onClick={() => {
|
||||
setAction('edit')
|
||||
form.setValues(row)
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label="Delete">
|
||||
<IconTrash
|
||||
className={classes.deleteIcon}
|
||||
width={20}
|
||||
height={20}
|
||||
onClick={() => {
|
||||
setAction('delete')
|
||||
form.setValues(row)
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const form = useForm<TBanner>({
|
||||
initialValues: {
|
||||
title: '',
|
||||
link: '',
|
||||
image: '',
|
||||
is_active: true,
|
||||
},
|
||||
|
||||
validate: (values) => ({
|
||||
title: values.title.length < 5 ? 'Length 5 characters or more' : null,
|
||||
// link: !/^(https?):\/\/[^\s/$.?#].[^\s]*$/.test(values.link)
|
||||
// ? "URL incorrect"
|
||||
// : null,
|
||||
image:
|
||||
values.image === null || values.image === ''
|
||||
? 'Image incorrect'
|
||||
: typeof values.image !== 'string' &&
|
||||
values.image &&
|
||||
values.image.size / 1024 / 1024 > 2
|
||||
? 'Image must be under 2MB in size.'
|
||||
: null,
|
||||
}),
|
||||
})
|
||||
|
||||
const getAllBanner = async () => {
|
||||
try {
|
||||
const res = await get(getBanners)
|
||||
if (res.status) {
|
||||
setData(res.data)
|
||||
}
|
||||
setLoader(false)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreate = async (formData: TBanner) => {
|
||||
try {
|
||||
const token = getAccessToken()
|
||||
const config = {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
const res = await create(createBanner, formData, getAllBanner, config)
|
||||
if (res === true) {
|
||||
setAction('')
|
||||
form.reset()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdate = async (formData: TBanner) => {
|
||||
try {
|
||||
const token = getAccessToken()
|
||||
const config = {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
|
||||
if (formData && formData.is_active == 1) {
|
||||
formData.is_active = true
|
||||
} else {
|
||||
formData.is_active = false
|
||||
}
|
||||
|
||||
if (typeof formData.image !== 'object') {
|
||||
delete formData.image
|
||||
}
|
||||
const res = await update(updateBanner, formData, getAllBanner, config)
|
||||
if (res === true) {
|
||||
setAction('')
|
||||
form.reset()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
const res = await Xdelete(deleteBanner, { id: id }, getAllBanner)
|
||||
if (res === true) {
|
||||
setAction('')
|
||||
form.reset()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllBanner()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (form.values.image) {
|
||||
setImageTemp(form.values.image)
|
||||
}
|
||||
}, [form.values.image])
|
||||
|
||||
return loader ? (
|
||||
<Box ta={'center'}>
|
||||
<Loader size={40} mt={'15%'} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box>
|
||||
<div className={classes.title}>
|
||||
<h3>
|
||||
<Text>Admin/</Text>
|
||||
Banners
|
||||
</h3>
|
||||
<Button
|
||||
m={5}
|
||||
onClick={() => {
|
||||
setAction('add')
|
||||
}}
|
||||
>
|
||||
+ Add banner
|
||||
</Button>
|
||||
</div>
|
||||
<Box pt={'md'}>
|
||||
{data.length > 0 ? (
|
||||
<DataTableAll data={data} columns={columns} size="" />
|
||||
) : (
|
||||
<Text
|
||||
mt={'30vh'}
|
||||
ta={'center'}
|
||||
c={'#dbdee3'}
|
||||
fw={600}
|
||||
fz={'1.5rem'}
|
||||
fs={'italic'}
|
||||
>
|
||||
Data Empty
|
||||
</Text>
|
||||
)}
|
||||
</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 banner' : 'Update banner'}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values: TBanner) => {
|
||||
setDisableBtn(true)
|
||||
action === 'edit'
|
||||
? await handleUpdate(values)
|
||||
: await handleCreate(values)
|
||||
setDisableBtn(false)
|
||||
})}
|
||||
>
|
||||
<Box pl={'md'} pr={'md'}>
|
||||
<TextInput
|
||||
label={'Title'}
|
||||
placeholder="Banner x"
|
||||
maxLength={255}
|
||||
required
|
||||
value={form.values.title}
|
||||
error={form.errors.title}
|
||||
onChange={(e) =>
|
||||
form.setFieldValue('title', e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label={'Link'}
|
||||
// required
|
||||
placeholder="https://www.abc.com"
|
||||
maxLength={255}
|
||||
value={form.values.link}
|
||||
error={form.errors.link}
|
||||
onChange={(e) =>
|
||||
form.setFieldValue('link', e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
|
||||
<FileInput
|
||||
label={'Image'}
|
||||
id="imageBanner"
|
||||
required
|
||||
accept="image/*"
|
||||
// valueComponent={ValueComponent}
|
||||
error={form.errors.image}
|
||||
onChange={(e) => form.setFieldValue('image', e)}
|
||||
/>
|
||||
|
||||
{action === 'edit' && typeof imageTemp === 'string' ? (
|
||||
<Image
|
||||
style={{ cursor: 'pointer' }}
|
||||
src={imageTemp}
|
||||
onClick={() => {
|
||||
const element = document.getElementById('imageBanner')
|
||||
element?.click()
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
typeof form.values.image === 'object' &&
|
||||
form.values.image !== null && (
|
||||
<Image
|
||||
style={{ cursor: 'pointer' }}
|
||||
src={URL.createObjectURL(form.values.image!)}
|
||||
w={250}
|
||||
m={'auto'}
|
||||
onClick={() => {
|
||||
const element = document.getElementById('imageBanner')
|
||||
element?.click()
|
||||
}}
|
||||
></Image>
|
||||
)
|
||||
)}
|
||||
<Checkbox
|
||||
label={
|
||||
<Text
|
||||
fz={'var(--input-label-size,var(--mantine-font-size-sm))'}
|
||||
fw={600}
|
||||
>
|
||||
Status
|
||||
</Text>
|
||||
}
|
||||
mt={'lg'}
|
||||
display={action === 'edit' ? 'block' : 'none'}
|
||||
checked={
|
||||
form.values.is_active || form.values.is_active === '1'
|
||||
? true
|
||||
: false
|
||||
}
|
||||
onChange={(e) =>
|
||||
form.setFieldValue('is_active', e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
<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>
|
||||
|
||||
{/* Dialog confirm delete */}
|
||||
<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 discount?
|
||||
<Group justify="center" m={10}>
|
||||
<Button
|
||||
disabled={disableBtn}
|
||||
fw={700}
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={async () => {
|
||||
setDisableBtn(true)
|
||||
await handleDelete(form.values.id!)
|
||||
setDisableBtn(false)
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
<Button
|
||||
fw={700}
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={() => setAction('')}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Text>
|
||||
</Dialog>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Banner
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
.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;
|
||||
}
|
||||
|
||||
.editIcon {
|
||||
color: rgb(9, 132, 132);
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
.deleteIcon {
|
||||
color: red;
|
||||
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);
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getContacts } from '@/api/Admin'
|
||||
import { Box, Loader, Text } from '@mantine/core'
|
||||
import classes from './Contacts.module.css'
|
||||
import { DataTablePagination } from '@/components/DataTable/DataTable'
|
||||
import { get } from '@/rtk/helpers/apiService'
|
||||
import moment from 'moment'
|
||||
const Contacts = () => {
|
||||
const [loader, setLoader] = useState(true)
|
||||
const [listContact, setListContact] = useState<any>()
|
||||
const columns = [
|
||||
{
|
||||
name: 'id',
|
||||
size: '5%',
|
||||
header: 'ID',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
size: '15%',
|
||||
header: 'Name',
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
size: '15%',
|
||||
header: 'Phone',
|
||||
},
|
||||
{
|
||||
name: 'company',
|
||||
size: '15%',
|
||||
header: 'Company',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
size: '30%',
|
||||
header: 'Email',
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
size: '20%',
|
||||
header: 'Created at',
|
||||
render: (row: any) => {
|
||||
return moment(row.created_at).format('YYYY/MM/DD - HH:mm:ss')
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const getListContact = async () => {
|
||||
try {
|
||||
const searchParams = new URLSearchParams(window.location.search)
|
||||
const params = {}
|
||||
|
||||
for (const [key, value] of searchParams.entries()) {
|
||||
if (key === 'page' && value === '') {
|
||||
Object.assign(params, { [`${key}`]: 1 })
|
||||
} else {
|
||||
Object.assign(params, { [`${key}`]: value })
|
||||
}
|
||||
}
|
||||
|
||||
const res = await get(getContacts, params)
|
||||
|
||||
if (res.status || res.status === 200) {
|
||||
setListContact(res)
|
||||
}
|
||||
setLoader(false)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getListContact()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{loader ? (
|
||||
<Box ta={'center'}>
|
||||
<Loader size={40} mt={'15%'} />
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
<div className={classes.title}>
|
||||
<h3>
|
||||
<Text>Admin/</Text>
|
||||
Contacts
|
||||
</h3>
|
||||
</div>
|
||||
<Box p={20}>
|
||||
<DataTablePagination
|
||||
filterInfo={[]}
|
||||
data={listContact}
|
||||
columns={columns}
|
||||
searchInput
|
||||
size=""
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Contacts
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
.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;
|
||||
}
|
||||
|
||||
.txtarea {
|
||||
height: 80vh;
|
||||
margin: rem(5);
|
||||
padding: rem(5);
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
import { getCustomTheme, updateCustomTheme } from '@/api/Admin'
|
||||
import { update } from '@/rtk/helpers/CRUD'
|
||||
import { get } from '@/rtk/helpers/apiService'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
import { Box, Button, Group, InputLabel, Loader, Text, useComputedColorScheme } from '@mantine/core'
|
||||
import CodeMirror from '@uiw/react-codemirror'
|
||||
import { useEffect, useState } from 'react'
|
||||
import classes from './CustomTheme.module.css'
|
||||
const CustomTheme = () => {
|
||||
const [data, setData] = useState({
|
||||
js: '',
|
||||
css: '',
|
||||
})
|
||||
const [textSelect, setTextSelect] = useState('js')
|
||||
const [loader, setLoader] = useState(true)
|
||||
const [activeBtn, setActiveBtn] = useState(false)
|
||||
const computedColorScheme = useComputedColorScheme('light', {
|
||||
getInitialValueInEffect: true,
|
||||
})
|
||||
const getCustomData = async () => {
|
||||
try {
|
||||
const res = await get(getCustomTheme)
|
||||
if (res.data !== null) {
|
||||
setData(res.data)
|
||||
}
|
||||
setLoader(false)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateSetting = async () => {
|
||||
try {
|
||||
await update(updateCustomTheme, data, getCustomData)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getCustomData()
|
||||
|
||||
}, [])
|
||||
|
||||
return loader ? (
|
||||
<Box ta={'center'}>
|
||||
<Loader size={40} mt={'15%'} />
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
<div className={classes.title}>
|
||||
<h3>
|
||||
<Text>Admin/</Text>
|
||||
Custom Theme
|
||||
</h3>
|
||||
<Group>
|
||||
<Button
|
||||
bg={textSelect !== 'js' ? 'white' : '#5df59f'}
|
||||
variant={textSelect !== 'js' ? 'outline' : 'filled'}
|
||||
onClick={() => setTextSelect('js')}
|
||||
>
|
||||
JS
|
||||
</Button>
|
||||
<Button
|
||||
bg={textSelect !== 'css' ? 'white' : '#5df59f'}
|
||||
variant={textSelect !== 'css' ? 'outline' : 'filled'}
|
||||
onClick={() => setTextSelect('css')}
|
||||
>
|
||||
CSS
|
||||
</Button>
|
||||
<Button
|
||||
m={5}
|
||||
disabled={activeBtn}
|
||||
onClick={async () => {
|
||||
setActiveBtn(true)
|
||||
await updateSetting()
|
||||
setActiveBtn(false)
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Group>
|
||||
</div>
|
||||
<Box p={20}>
|
||||
<div className={classes.box}>
|
||||
<div style={{ display: textSelect === 'js' ? 'block' : 'none' }}>
|
||||
<InputLabel p={5} size="md">
|
||||
JavaScript
|
||||
</InputLabel>
|
||||
<CodeMirror
|
||||
value={data.js ? data.js : ''}
|
||||
height="100%"
|
||||
className={classes.txtarea}
|
||||
extensions={[javascript({ jsx: true })]}
|
||||
theme={computedColorScheme === 'light' ? 'light' : 'dark'}
|
||||
onChange={(e) => {
|
||||
setData({ ...data, js: e })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: textSelect === 'css' ? 'block' : 'none' }}>
|
||||
<InputLabel p={5} size="md">
|
||||
CSS
|
||||
</InputLabel>
|
||||
<CodeMirror
|
||||
value={data.css ? data.css : ''}
|
||||
height="100%"
|
||||
className={classes.txtarea}
|
||||
extensions={[javascript({ jsx: true })]}
|
||||
theme={computedColorScheme === 'light' ? 'light' : 'dark'}
|
||||
onChange={(e) => {
|
||||
setData({ ...data, css: e })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomTheme
|
||||
|
|
@ -1,242 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { statisticRevenuesByMonth, statisticSearchSNByMonth } from '@/api/Admin'
|
||||
import { get } from '@/rtk/helpers/apiService'
|
||||
import { Box, Grid, Paper, Text } from '@mantine/core'
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
ComposedChart,
|
||||
Legend,
|
||||
Rectangle,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from 'recharts'
|
||||
import classes from './Dashboard.module.css'
|
||||
type TData = {
|
||||
month: string | number
|
||||
year: string | number
|
||||
timezone_offset: string
|
||||
}
|
||||
|
||||
const Chart = () => {
|
||||
const [listStatisticSNKeywords, setListStatisticSNKeywords] = useState<any>(
|
||||
[],
|
||||
)
|
||||
const [listStatisticRevenue, setListStatisticRevenue] = useState<any>([])
|
||||
|
||||
const [filterDataStatistic] = useState<TData>(() => {
|
||||
const currentDate = new Date()
|
||||
const currentMonth = currentDate.getMonth() + 1
|
||||
const currentYear = currentDate.getFullYear()
|
||||
const timezoneOffsetMinutes = currentDate.getTimezoneOffset()
|
||||
const timezoneOffsetHours = Math.floor(Math.abs(timezoneOffsetMinutes) / 60)
|
||||
const timezoneOffsetSign = timezoneOffsetMinutes > 0 ? '-' : '+'
|
||||
const timezoneOffset = `${timezoneOffsetSign}${String(
|
||||
timezoneOffsetHours,
|
||||
).padStart(2, '0')}:${String(Math.abs(timezoneOffsetMinutes) % 60).padStart(
|
||||
2,
|
||||
'0',
|
||||
)}`
|
||||
|
||||
return {
|
||||
month: currentMonth,
|
||||
year: currentYear,
|
||||
timezone_offset: timezoneOffset,
|
||||
}
|
||||
})
|
||||
|
||||
const handleFetchDataStatisticSNKeywords = async () => {
|
||||
try {
|
||||
const res = await get(statisticSearchSNByMonth, filterDataStatistic)
|
||||
|
||||
if (res.status) {
|
||||
const data = Object.keys(res.data)?.map((i) => ({
|
||||
name: i,
|
||||
count: res.data[i].count,
|
||||
count_complete: res.data[i].count_complete,
|
||||
count_pending: res.data[i].count_pending,
|
||||
count_fail: res.data[i].count_fail,
|
||||
}))
|
||||
setListStatisticSNKeywords(data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
const handleFetchDataStatisticRevenue = async () => {
|
||||
try {
|
||||
const res = await get(statisticRevenuesByMonth, filterDataStatistic)
|
||||
|
||||
if (res.status) {
|
||||
const data = Object.keys(res.data)?.map((i) => ({
|
||||
name: i,
|
||||
total_price: parseFloat(res.data[i].total_price),
|
||||
}))
|
||||
setListStatisticRevenue(data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const customFormatter = (value: string) => {
|
||||
const customNames = {
|
||||
count: 'Total',
|
||||
count_complete: 'Completed',
|
||||
count_pending: 'Pending',
|
||||
count_fail: 'Failed',
|
||||
total_price: 'Total Price',
|
||||
} as { [key: string]: string }
|
||||
|
||||
return customNames?.[value] || value
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleFetchDataStatisticSNKeywords()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
handleFetchDataStatisticRevenue()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Box className={classes.chartbox}>
|
||||
{/* Search SN Chart */}
|
||||
<Grid>
|
||||
<Grid.Col span={{ base: 12, xl: 6 }}>
|
||||
<Paper withBorder radius="md" className={classes.paper}>
|
||||
<Text p={10} className={classes.titleChart}>
|
||||
Check SN Chart
|
||||
</Text>
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
height={400}
|
||||
className={classes.chart}
|
||||
>
|
||||
<ComposedChart
|
||||
height={400}
|
||||
data={listStatisticSNKeywords}
|
||||
margin={{
|
||||
top: 5,
|
||||
right: 30,
|
||||
left: 0,
|
||||
bottom: 5,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid stroke="#f5f5f5" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip
|
||||
content={(props) => {
|
||||
const { payload, label } = props
|
||||
|
||||
return (
|
||||
<div className={classes.customTooltip}>
|
||||
<p>{label}</p>
|
||||
{payload?.map((entry, index) => (
|
||||
<p key={index} style={{ color: entry.color }}>
|
||||
{customFormatter(entry?.name as string)}:{' '}
|
||||
{entry.value}
|
||||
</p>
|
||||
))}
|
||||
<p
|
||||
style={{
|
||||
color: 'green',
|
||||
}}
|
||||
>
|
||||
Complete:{' '}
|
||||
{payload &&
|
||||
payload[0] &&
|
||||
payload[0].payload.count_complete}
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
color: 'blue',
|
||||
}}
|
||||
>
|
||||
Pending:{' '}
|
||||
{payload &&
|
||||
payload[0] &&
|
||||
payload[0].payload.count_pending}
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
color: 'red',
|
||||
}}
|
||||
>
|
||||
Failed:{' '}
|
||||
{payload &&
|
||||
payload[0] &&
|
||||
payload[0].payload.count_fail}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Legend formatter={(value) => customFormatter(value)} />
|
||||
<Bar dataKey="count" barSize={20} fill="#ff7300" />
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</Paper>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{ base: 12, xl: 6 }}>
|
||||
{/* Revenue Chart */}
|
||||
<Paper withBorder radius="md" className={classes.paper}>
|
||||
<Text p={10} className={classes.titleChart}>
|
||||
Revenues Chart
|
||||
</Text>
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
height={400}
|
||||
className={classes.chart}
|
||||
>
|
||||
<BarChart
|
||||
height={400}
|
||||
data={listStatisticRevenue}
|
||||
margin={{
|
||||
top: 5,
|
||||
right: 30,
|
||||
left: 0,
|
||||
bottom: 5,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip
|
||||
content={(props) => {
|
||||
const { payload, label } = props
|
||||
return (
|
||||
<div className={classes.customTooltip}>
|
||||
<p>{label}</p>
|
||||
{payload?.map((entry, index) => (
|
||||
<p key={index} style={{ color: entry.color }}>
|
||||
{customFormatter(entry?.name as string)}:{' $'}
|
||||
{entry.value}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Legend formatter={(value) => customFormatter(value)} />
|
||||
<Bar
|
||||
dataKey="total_price"
|
||||
fill="#8884d8"
|
||||
activeBar={<Rectangle fill="#7dd3fc" stroke="blue" />}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Paper>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Chart
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
.label {
|
||||
font-family:
|
||||
Greycliff CF,
|
||||
var(--mantine-font-family);
|
||||
}
|
||||
|
||||
.root {
|
||||
padding: calc(var(--mantine-spacing-xl) * 1.5);
|
||||
}
|
||||
|
||||
|
||||
.value {
|
||||
font-size: rem(48px);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: white;
|
||||
/* color: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-3)); */
|
||||
}
|
||||
|
||||
.titleItemDashboard {
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.chartBox {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
|
||||
.tooltipChart {
|
||||
background-color: white;
|
||||
padding: 4px;
|
||||
border-radius: 6px;
|
||||
|
||||
}
|
||||
|
||||
.cardDashboard {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.buttonMoreInfo {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
color: white;
|
||||
padding: 6px 0
|
||||
}
|
||||
|
||||
.buttonMoreInfo:hover {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.paper {
|
||||
width: 100%;
|
||||
background-color: light-dark(var(white), var(--mantine-color-dark-7));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.titleChart {
|
||||
font-size: rem(24px);
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-family:
|
||||
Greycliff CF,
|
||||
var(--mantine-font-family);
|
||||
background-color: light-dark(var(white), var(--mantine-color-dark-7));
|
||||
}
|
||||
|
||||
.iconMoreInfo {
|
||||
color: white
|
||||
}
|
||||
|
||||
.customTooltip {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chart {
|
||||
background-color: light-dark(var(white), var(--mantine-color-dark-7));
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
const Dashboard = () => {
|
||||
// type Tdata = {
|
||||
// title: string
|
||||
// value: number
|
||||
// url: string
|
||||
// icon: any
|
||||
// }
|
||||
|
||||
// const navigate = useNavigate()
|
||||
// const [dashboardData, setDashboardData] = useState<Tdata[]>([])
|
||||
// const [loader, setLoader] = useState(true)
|
||||
// const getDashboardInfo = async () => {
|
||||
// try {
|
||||
// const res = await get(getDashboard)
|
||||
|
||||
// const data = [
|
||||
// {
|
||||
// title: 'Discounts',
|
||||
// value: res.data.discounts,
|
||||
// url: '/discounts',
|
||||
// icon: IconDiscount2,
|
||||
// },
|
||||
// {
|
||||
// title: 'Orders',
|
||||
// value: res.data.orders,
|
||||
// url: '/order',
|
||||
// icon: IconReceipt2,
|
||||
// },
|
||||
// {
|
||||
// title: 'Clients',
|
||||
// value: res.data.clients,
|
||||
// url: '/client',
|
||||
// icon: IconUserPlus,
|
||||
// },
|
||||
// {
|
||||
// title: 'Contacts',
|
||||
// value: res.data.contacts,
|
||||
// url: '/contacts',
|
||||
// icon: IconAddressBook,
|
||||
// },
|
||||
// ]
|
||||
// setDashboardData(data)
|
||||
// setLoader(false)
|
||||
// } catch (error) {
|
||||
// console.log(error)
|
||||
// }
|
||||
// }
|
||||
|
||||
// useEffect(() => {
|
||||
// // Check if dashboardData is already populated
|
||||
// if (dashboardData.length === 0) {
|
||||
// getDashboardInfo()
|
||||
// }
|
||||
// }, [dashboardData])
|
||||
|
||||
// const items = dashboardData.map((item, index) => {
|
||||
// const colors = ['#38bdf8', '#34d399', '#f59e0b', '#ef4444']
|
||||
// const Icon = item.icon
|
||||
// return (
|
||||
// <Flex
|
||||
// key={index}
|
||||
// bg={colors[index % 100]}
|
||||
// direction="column"
|
||||
// justify="space-between"
|
||||
// className={classes.cardDashboard}
|
||||
// >
|
||||
// <Group justify="space-between" p="md">
|
||||
// <Text
|
||||
// size="1.8rem"
|
||||
// c="#FFFFFF"
|
||||
// className={classes.titleItemDashboard}
|
||||
// >
|
||||
// {item.title}
|
||||
// </Text>
|
||||
// <Icon className={classes.icon} size="3.5rem" stroke={1.5} />
|
||||
// </Group>
|
||||
// <Center pb="md">
|
||||
// <Text className={classes.value} c="#FFFFFF">
|
||||
// {item.value}
|
||||
// </Text>
|
||||
// </Center>
|
||||
// <Center
|
||||
// className={classes.buttonMoreInfo}
|
||||
// onClick={() => navigate(item.url)}
|
||||
// >
|
||||
// More info
|
||||
// <IconArrowNarrowRight
|
||||
// color="#FFFFFF"
|
||||
// className={classes.iconMoreInfo}
|
||||
// />
|
||||
// </Center>
|
||||
// </Flex>
|
||||
// )
|
||||
// })
|
||||
return (
|
||||
<div>
|
||||
{/* {loader ? (
|
||||
<Box ta={'center'}>
|
||||
<Loader size={40} mt={'15%'} />
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
<Box>
|
||||
<div>
|
||||
<h3>
|
||||
<Text>Admin/</Text>
|
||||
Dashboard
|
||||
</h3>
|
||||
</div>
|
||||
</Box>
|
||||
<Grid>
|
||||
<Grid.Col span={12}>
|
||||
<SimpleGrid
|
||||
className={classes.h100}
|
||||
cols={{ base: 1, md: 2, lg: 4 }}
|
||||
>
|
||||
{items}
|
||||
</SimpleGrid>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={12}>
|
||||
<Chart />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</>
|
||||
)} */}
|
||||
Dashboard
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Dashboard
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
.box {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.divImage {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-color: light-dark(#f1f3f5, var(--mantine-color-dark-6));
|
||||
}
|
||||
|
||||
.arrowIcon {
|
||||
color: #00cf00;
|
||||
}
|
||||
|
||||
.groupBox {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.groupBox > div {
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.boxFile {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fileInput {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.labelTooltip {
|
||||
font-size: 0.8rem;
|
||||
color: white;
|
||||
}
|
||||
|
|
@ -1,289 +0,0 @@
|
|||
import {
|
||||
clearCacheAPI,
|
||||
getGeneralSetting,
|
||||
updateGeneralSetting,
|
||||
} from '@/api/Admin'
|
||||
import RenderForm from '@/components/RenderForm/RenderForm'
|
||||
import { update } from '@/rtk/helpers/CRUD'
|
||||
import { get } from '@/rtk/helpers/apiService'
|
||||
import { getAccessToken } from '@/rtk/localStorage'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
FileInput,
|
||||
Group,
|
||||
Image,
|
||||
Loader,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from '@mantine/core'
|
||||
import { useForm } from '@mantine/form'
|
||||
import { notifications } from '@mantine/notifications'
|
||||
import { IconArrowBigRightFilled, IconPhoto } from '@tabler/icons-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import classes from './GeneralSetting.module.css'
|
||||
import { generalSettingForm } from '@/variables/formFormat'
|
||||
import { createInitialValue } from '@/utils/createInitialValue'
|
||||
import { TGeneralSettingValues } from '@/variables/types'
|
||||
|
||||
const GeneralSetting = () => {
|
||||
const formFormat = generalSettingForm
|
||||
let initValue = createInitialValue(formFormat.format, {
|
||||
favicon: '',
|
||||
logo: '',
|
||||
}) as TGeneralSettingValues
|
||||
|
||||
const form = useForm<TGeneralSettingValues>({
|
||||
initialValues: initValue,
|
||||
validate: (values) => ({
|
||||
email:
|
||||
values.email === ''
|
||||
? 'Email is required'
|
||||
: /^\S+@\S+$/.test(values.email)
|
||||
? null
|
||||
: 'Invalid email',
|
||||
}),
|
||||
})
|
||||
|
||||
const [oldImage, setOldImage] = useState({
|
||||
favicon: '',
|
||||
logo: '',
|
||||
})
|
||||
|
||||
const [loader, setLoader] = useState(true)
|
||||
const [activeBtn, setActiveBtn] = useState(false)
|
||||
|
||||
const getSetting = async () => {
|
||||
try {
|
||||
const res = await get(getGeneralSetting)
|
||||
if (res.status && res.data !== null) {
|
||||
Object.keys(res.data).forEach((key) => {
|
||||
res.data[key] = res.data[key] === null ? '' : res.data[key]
|
||||
})
|
||||
form.setValues(res.data)
|
||||
setOldImage({
|
||||
...oldImage,
|
||||
favicon: res.data.favicon,
|
||||
logo: res.data.logo,
|
||||
})
|
||||
}
|
||||
setLoader(false)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = form.onSubmit(async (values) => {
|
||||
setActiveBtn(true)
|
||||
try {
|
||||
const formData = new FormData()
|
||||
|
||||
Object.keys(values).forEach((key) => {
|
||||
const dynamicKey = key as keyof typeof values
|
||||
const value = values[dynamicKey]!
|
||||
if (!['favicon', 'logo'].includes(key)) {
|
||||
formData.append(key, value)
|
||||
}
|
||||
})
|
||||
|
||||
if (values.favicon !== null && typeof values.favicon === 'object') {
|
||||
formData.append('favicon', values.favicon)
|
||||
}
|
||||
|
||||
if (values.logo !== null && typeof values.logo === 'object') {
|
||||
formData.append('logo', values.logo)
|
||||
}
|
||||
|
||||
const token = getAccessToken()
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
Authorization: `Bearer ${token}`,
|
||||
}
|
||||
|
||||
await update(updateGeneralSetting, formData, getSetting, {
|
||||
headers: headers,
|
||||
})
|
||||
setActiveBtn(false)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
})
|
||||
|
||||
const clearCache = async () => {
|
||||
try {
|
||||
const res = await get(clearCacheAPI)
|
||||
if (res.status) {
|
||||
notifications.show({
|
||||
title: 'Success',
|
||||
message: 'Success',
|
||||
color: 'green',
|
||||
})
|
||||
}
|
||||
if (res.status === false) {
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: 'Clear cache fail',
|
||||
color: 'red',
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getSetting()
|
||||
}, [])
|
||||
|
||||
return loader ? (
|
||||
<Box ta={'center'}>
|
||||
<Loader size={40} mt={'15%'} />
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
<div className={classes.title}>
|
||||
<h3>
|
||||
<Text>Admin/</Text>General Setting
|
||||
</h3>
|
||||
<Group>
|
||||
<Button mt={'lg'} disabled={activeBtn} onClick={() => handleSubmit()}>
|
||||
Save
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
bg={'cyan'}
|
||||
mt={'lg'}
|
||||
disabled={activeBtn}
|
||||
onClick={async () => {
|
||||
setActiveBtn(true)
|
||||
await clearCache()
|
||||
setActiveBtn(false)
|
||||
}}
|
||||
>
|
||||
Clear Cache
|
||||
</Button>
|
||||
</Group>
|
||||
</div>
|
||||
<form>
|
||||
<RenderForm
|
||||
column={formFormat.column}
|
||||
form={form}
|
||||
formFormat={formFormat.format}
|
||||
/>
|
||||
<Box className={classes.groupBox}>
|
||||
<Box
|
||||
className={classes.boxFile}
|
||||
onClick={() => {
|
||||
document.getElementById('faviconInput')?.click()
|
||||
}}
|
||||
>
|
||||
<FileInput
|
||||
leftSection={<IconPhoto />}
|
||||
label="Favicon"
|
||||
mt="md"
|
||||
accept="image/*"
|
||||
variant="filled"
|
||||
onChange={(e) => {
|
||||
form.setFieldValue('favicon', e)
|
||||
}}
|
||||
id="faviconInput"
|
||||
/>
|
||||
|
||||
{/* Favicon display processing */}
|
||||
<Tooltip
|
||||
label={
|
||||
<Text className={classes.labelTooltip}>Change favicon</Text>
|
||||
}
|
||||
offset={-20}
|
||||
color="gray"
|
||||
>
|
||||
<div className={classes.divImage}>
|
||||
{oldImage.favicon !== '' && (
|
||||
<>
|
||||
<Box ta={'center'}>
|
||||
<Image src={oldImage.favicon} w={200} m={10}></Image>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{form.values.favicon !== null &&
|
||||
typeof form.values.favicon === 'object' && (
|
||||
<>
|
||||
<IconArrowBigRightFilled
|
||||
className={classes.arrowIcon}
|
||||
width={40}
|
||||
height={50}
|
||||
/>
|
||||
<Box ta={'center'}>
|
||||
<Image
|
||||
src={URL.createObjectURL(form.values.favicon!)}
|
||||
w={200}
|
||||
m={10}
|
||||
></Image>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
className={classes.boxFile}
|
||||
onClick={() => {
|
||||
document.getElementById('logoInput')?.click()
|
||||
}}
|
||||
>
|
||||
<FileInput
|
||||
leftSection={<IconPhoto />}
|
||||
label="Logo"
|
||||
mt="md"
|
||||
accept="image/*"
|
||||
variant="filled"
|
||||
onChange={(e) => {
|
||||
form.setFieldValue('logo', e)
|
||||
}}
|
||||
className={'fileInput'}
|
||||
id="logoInput"
|
||||
/>
|
||||
{/* Logo display processing */}
|
||||
<Tooltip
|
||||
label={<Text className={classes.labelTooltip}>Change logo</Text>}
|
||||
offset={-20}
|
||||
color="gray"
|
||||
>
|
||||
<div className={classes.divImage}>
|
||||
{oldImage.logo !== '' && (
|
||||
<>
|
||||
<Box ta={'center'}>
|
||||
<Image src={oldImage.logo} w={200} m={10}></Image>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{form.values.logo !== null &&
|
||||
typeof form.values.logo === 'object' && (
|
||||
<>
|
||||
<IconArrowBigRightFilled
|
||||
className={classes.arrowIcon}
|
||||
width={40}
|
||||
height={50}
|
||||
/>
|
||||
<Box>
|
||||
<Image
|
||||
src={URL.createObjectURL(form.values.logo!)}
|
||||
w={200}
|
||||
m={10}
|
||||
></Image>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default GeneralSetting
|
||||
8
FRONTEND/src/pages/Banner/Banner.module.css → FRONTEND/src/pages/UsersManagement/UsersManagement.module.css
Executable file → Normal file
8
FRONTEND/src/pages/Banner/Banner.module.css → FRONTEND/src/pages/UsersManagement/UsersManagement.module.css
Executable file → Normal file
|
|
@ -14,15 +14,15 @@
|
|||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.editIcon {
|
||||
color: rgb(9, 132, 132);
|
||||
.deleteIcon {
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
.deleteIcon {
|
||||
color: red;
|
||||
.editIcon {
|
||||
color: rgb(9, 132, 132);
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
border-radius: 25%;
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
import { createOrUpdateUser, deleteUser, getAllUsers } from '@/api/Admin'
|
||||
import { ButtonCopy } from '@/components/CopyClipboard/CopyClipboard'
|
||||
import DataTableAll from '@/components/DataTable/DataTable'
|
||||
import { get, post } from '@/rtk/helpers/apiService'
|
||||
import { update, Xdelete } from '@/rtk/helpers/CRUD'
|
||||
import { TUser } from '@/variables/types'
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Code,
|
||||
Dialog,
|
||||
Group,
|
||||
Modal,
|
||||
MultiSelect,
|
||||
Text,
|
||||
TextInput
|
||||
} from '@mantine/core'
|
||||
import { useForm } from '@mantine/form'
|
||||
import { IconEdit, IconX } from '@tabler/icons-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import classes from './UsersManagement.module.css'
|
||||
const UsersManagement = () => {
|
||||
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 columns = [
|
||||
{
|
||||
name: 'id',
|
||||
size: '3%',
|
||||
header: 'ID',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
size: '17%',
|
||||
header: 'Name',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
size: '26%',
|
||||
header: 'Email',
|
||||
},
|
||||
{
|
||||
name: 'permission',
|
||||
size: '10%',
|
||||
header: 'Permission',
|
||||
render: (row: TUser) => {
|
||||
if (row.permission.includes(',')) {
|
||||
return row?.permission?.split(',').map((p) => {
|
||||
return <Badge mr={'sm'}>{p}</Badge>
|
||||
})
|
||||
} else {
|
||||
return <Badge>{row.permission}</Badge>
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '#',
|
||||
size: '5%',
|
||||
header: 'Action',
|
||||
render: (row: TUser) => {
|
||||
return (
|
||||
<Box className={classes.optionIcon}>
|
||||
<IconEdit
|
||||
className={classes.editIcon}
|
||||
onClick={() => {
|
||||
setAction('edit')
|
||||
form.setValues(row)
|
||||
}}
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<IconX
|
||||
className={classes.deleteIcon}
|
||||
onClick={() => {
|
||||
setAction('delete')
|
||||
setItem(row)
|
||||
}}
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
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()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={classes.title}>
|
||||
<h3>
|
||||
<Text>Admin/</Text>
|
||||
Users Management
|
||||
</h3>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setAction('add')
|
||||
form.reset()
|
||||
}}
|
||||
>
|
||||
+ Add
|
||||
</Button>
|
||||
</div>
|
||||
<Box mt={'md'}>
|
||||
<DataTableAll data={users} columns={columns} size="" searchInput />
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
export default UsersManagement
|
||||
|
|
@ -3,15 +3,13 @@ import ResetPassword from '@/components/Authentication/ResetPassword'
|
|||
import BasePage from '@/components/BasePage/BasePage'
|
||||
import ProtectedRoute from '@/components/ProtectedRoute/ProtectedRoute'
|
||||
import PageLogin from '@/pages/Auth/Login/Login'
|
||||
import CustomTheme from '@/pages/CustomTheme/CustomTheme'
|
||||
import Dashboard from '@/pages/Dashboard/Dashboard'
|
||||
import GeneralSetting from '@/pages/GeneralSetting/GeneralSetting'
|
||||
import LeaveManagement from '@/pages/LeaveManagement/LeaveManagement'
|
||||
import PageNotFound from '@/pages/NotFound/NotFound'
|
||||
import Tickets from '@/pages/Tickets/Tickets'
|
||||
import TicketsManagement from '@/pages/TicketsManagement/TicketsManagement'
|
||||
import Timekeeping from '@/pages/Timekeeping/Timekeeping'
|
||||
import Tracking from '@/pages/Tracking/Tracking'
|
||||
import UsersManagement from '@/pages/UsersManagement/UsersManagement'
|
||||
import PageWelcome from '@/pages/Welcome/Welcome'
|
||||
import Worklogs from '@/pages/Worklogs/Worklogs'
|
||||
import { Navigate } from 'react-router-dom'
|
||||
|
|
@ -34,48 +32,6 @@ const mainRoutes = [
|
|||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
element: (
|
||||
<ProtectedRoute mode="route" permission="staff">
|
||||
<BasePage
|
||||
main={
|
||||
<>
|
||||
<Dashboard />
|
||||
</>
|
||||
}
|
||||
></BasePage>
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/general-setting',
|
||||
element: (
|
||||
<ProtectedRoute mode="home" permission="staff">
|
||||
<BasePage
|
||||
main={
|
||||
<>
|
||||
<GeneralSetting />
|
||||
</>
|
||||
}
|
||||
></BasePage>
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/custom-theme',
|
||||
element: (
|
||||
<ProtectedRoute mode="home" permission="staff">
|
||||
<BasePage
|
||||
main={
|
||||
<>
|
||||
<CustomTheme />
|
||||
</>
|
||||
}
|
||||
></BasePage>
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/tracking',
|
||||
element: (
|
||||
|
|
@ -174,6 +130,20 @@ const mainRoutes = [
|
|||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
element: (
|
||||
<ProtectedRoute mode="route" permission="admin,hr">
|
||||
<BasePage
|
||||
main={
|
||||
<>
|
||||
<UsersManagement/>
|
||||
</>
|
||||
}
|
||||
></BasePage>
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// path: '/packages',
|
||||
// element: (
|
||||
|
|
|
|||
|
|
@ -69,4 +69,11 @@ export type TOrderValues = {
|
|||
package_name?: string
|
||||
package_price?: string
|
||||
discounted_package_price?: string
|
||||
}
|
||||
|
||||
export type TUser = {
|
||||
id: number;
|
||||
email: string;
|
||||
name: string;
|
||||
permission: string
|
||||
}
|
||||
Loading…
Reference in New Issue