Restyle users and profile workspace pages
This commit is contained in:
parent
82d5f49261
commit
5cc34803fe
@ -1,180 +1,265 @@
|
||||
import {
|
||||
mdiChartTimelineVariant,
|
||||
mdiUpload,
|
||||
} from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
|
||||
import CardBox from '../components/CardBox';
|
||||
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||
import SectionMain from '../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../config';
|
||||
|
||||
import { mdiUpload } from '@mdi/js';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../components/FormField';
|
||||
import BaseDivider from '../components/BaseDivider';
|
||||
import BaseButtons from '../components/BaseButtons';
|
||||
import BaseButton from '../components/BaseButton';
|
||||
import FormCheckRadio from '../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../components/FormCheckRadioGroup';
|
||||
import FormImagePicker from '../components/FormImagePicker';
|
||||
import { SwitchField } from '../components/SwitchField';
|
||||
import { SelectField } from '../components/SelectField';
|
||||
|
||||
import { update, fetch } from '../stores/users/usersSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||
import Head from 'next/head';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import {findMe} from "../stores/authSlice";
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import FormImagePicker from '../components/FormImagePicker';
|
||||
import { SelectField } from '../components/SelectField';
|
||||
import { SwitchField } from '../components/SwitchField';
|
||||
import { getPageTitle } from '../config';
|
||||
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||
import { findMe } from '../stores/authSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||
import { update } from '../stores/users/usersSlice';
|
||||
|
||||
type ProfileValues = {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
phoneNumber: string;
|
||||
email: string;
|
||||
app_role: any;
|
||||
disabled: boolean;
|
||||
avatar: any[];
|
||||
password: string;
|
||||
};
|
||||
|
||||
const initVals: ProfileValues = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
phoneNumber: '',
|
||||
email: '',
|
||||
app_role: '',
|
||||
disabled: false,
|
||||
avatar: [],
|
||||
password: '',
|
||||
};
|
||||
|
||||
function Panel({
|
||||
children,
|
||||
className = '',
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className={`rounded-lg border border-[#19192d]/10 bg-white ${className}`}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function FieldWrap({
|
||||
label,
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<label className='block'>
|
||||
<span className='mb-2 block text-sm font-semibold text-[#19192d]'>
|
||||
{label}
|
||||
</span>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
const inputClassName =
|
||||
'w-full rounded-lg border border-[#19192d]/10 bg-[#fffdf9] px-3 py-2 text-sm text-[#19192d] outline-none focus:border-[#35b7a5] focus:ring-2 focus:ring-[#35b7a5]/15 disabled:bg-[#fbf8f1] disabled:text-[#72798a]';
|
||||
|
||||
const EditUsers = () => {
|
||||
const { currentUser, isFetching, token } = useAppSelector(
|
||||
(state) => state.auth,
|
||||
);
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const notify = (type, msg) => toast(msg, { type });
|
||||
const initVals = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
phoneNumber: '',
|
||||
email: '',
|
||||
app_role: '',
|
||||
disabled: false,
|
||||
avatar: [],
|
||||
password: ''
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser?.id && typeof currentUser === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
useEffect(() => {
|
||||
if (currentUser?.id && typeof currentUser === 'object') {
|
||||
const newInitialVal = { ...initVals } as Record<string, any>;
|
||||
|
||||
Object.keys(initVals).forEach(
|
||||
(el) => (newInitialVal[el] = currentUser[el]),
|
||||
);
|
||||
Object.keys(initVals).forEach((key) => {
|
||||
newInitialVal[key] = currentUser[key];
|
||||
});
|
||||
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [currentUser]);
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: currentUser.id, data }));
|
||||
await dispatch(findMe());
|
||||
await router.push('/users/users-list');
|
||||
notify('success', 'Profile was updated!');
|
||||
};
|
||||
const handleSubmit = async (data: ProfileValues) => {
|
||||
await dispatch(update({ id: currentUser.id, data }));
|
||||
await dispatch(findMe());
|
||||
await router.push('/users/users-list');
|
||||
toast('Profile was updated!', { type: 'success' });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit profile')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Edit profile'
|
||||
main
|
||||
const avatarUrl = currentUser?.avatar?.[0]?.publicUrl;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit profile')}</title>
|
||||
</Head>
|
||||
|
||||
<main className='mx-auto max-w-5xl px-6 py-6'>
|
||||
<div className='mb-4 rounded-lg bg-[#19192d] p-5 text-white'>
|
||||
<div className='flex items-center gap-3 text-[#b17a1e]'>
|
||||
<span className='text-lg'>●</span>
|
||||
<span className='text-xs font-semibold uppercase tracking-[0.22em]'>
|
||||
Account
|
||||
</span>
|
||||
</div>
|
||||
<h1 className='mt-3 text-xl font-semibold'>Profile</h1>
|
||||
<p className='mt-2 max-w-2xl text-sm leading-6 text-[#fffdf9]'>
|
||||
Update your workspace profile, avatar, role, and password.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Panel className='p-5'>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form className='grid gap-5'>
|
||||
<div className='grid gap-5 md:grid-cols-[220px_1fr]'>
|
||||
<div>
|
||||
<p className='text-sm font-semibold text-[#19192d]'>Avatar</p>
|
||||
<div className='mt-3 flex h-32 w-32 items-center justify-center overflow-hidden rounded-full border border-[#19192d]/10 bg-[#f3fbf8]'>
|
||||
{avatarUrl ? (
|
||||
<Image
|
||||
className='h-full w-full object-cover object-center'
|
||||
src={avatarUrl}
|
||||
alt='Avatar'
|
||||
width={128}
|
||||
height={128}
|
||||
unoptimized
|
||||
/>
|
||||
) : (
|
||||
<span className='text-3xl font-semibold text-[#35b7a5]'>
|
||||
{currentUser?.firstName?.[0] ||
|
||||
currentUser?.email?.[0] ||
|
||||
'U'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className='mt-4'>
|
||||
<Field
|
||||
label='Avatar'
|
||||
color='info'
|
||||
icon={mdiUpload}
|
||||
path='users/avatar'
|
||||
name='avatar'
|
||||
id='avatar'
|
||||
schema={{
|
||||
size: undefined,
|
||||
formats: undefined,
|
||||
}}
|
||||
component={FormImagePicker}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='grid gap-4 md:grid-cols-2'>
|
||||
<FieldWrap label='First name'>
|
||||
<Field
|
||||
className={inputClassName}
|
||||
name='firstName'
|
||||
placeholder='First name'
|
||||
/>
|
||||
</FieldWrap>
|
||||
|
||||
<FieldWrap label='Last name'>
|
||||
<Field
|
||||
className={inputClassName}
|
||||
name='lastName'
|
||||
placeholder='Last name'
|
||||
/>
|
||||
</FieldWrap>
|
||||
|
||||
<FieldWrap label='Phone number'>
|
||||
<Field
|
||||
className={inputClassName}
|
||||
name='phoneNumber'
|
||||
placeholder='Phone number'
|
||||
/>
|
||||
</FieldWrap>
|
||||
|
||||
<FieldWrap label='Email'>
|
||||
<Field
|
||||
className={inputClassName}
|
||||
name='email'
|
||||
placeholder='Email'
|
||||
disabled
|
||||
/>
|
||||
</FieldWrap>
|
||||
|
||||
<FieldWrap label='App role'>
|
||||
<Field
|
||||
name='app_role'
|
||||
id='app_role'
|
||||
component={SelectField}
|
||||
options={initialValues.app_role}
|
||||
itemRef='roles'
|
||||
showField='name'
|
||||
/>
|
||||
</FieldWrap>
|
||||
|
||||
<FieldWrap label='Password'>
|
||||
<Field
|
||||
className={inputClassName}
|
||||
name='password'
|
||||
placeholder='New password'
|
||||
/>
|
||||
</FieldWrap>
|
||||
|
||||
<div className='md:col-span-2'>
|
||||
<FieldWrap label='Disabled'>
|
||||
<Field
|
||||
name='disabled'
|
||||
id='disabled'
|
||||
component={SwitchField}
|
||||
/>
|
||||
</FieldWrap>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-wrap justify-end gap-3 border-t border-[#19192d]/10 pt-5'>
|
||||
<button
|
||||
type='button'
|
||||
className='rounded-full border border-[#19192d]/10 bg-white px-4 py-2 text-sm font-semibold text-[#19192d]'
|
||||
onClick={() => router.push('/users/users-list')}
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
{currentUser?.avatar[0]?.publicUrl && <div className={'grid grid-cols-6 gap-4 mb-4'}>
|
||||
<div className="col-span-1 w-80 h-80 overflow-hidden border-2 rounded-full inline-flex items-center justify-center mb-8">
|
||||
<img className="w-80 h-80 max-w-full max-h-full object-cover object-center" src={`${currentUser?.avatar[0]?.publicUrl}`} alt="Avatar" />
|
||||
</div>
|
||||
</div>}
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField>
|
||||
<Field
|
||||
label='Avatar'
|
||||
color='info'
|
||||
icon={mdiUpload}
|
||||
path={'users/avatar'}
|
||||
name='avatar'
|
||||
id='avatar'
|
||||
schema={{
|
||||
size: undefined,
|
||||
formats: undefined,
|
||||
}}
|
||||
component={FormImagePicker}
|
||||
></Field>
|
||||
</FormField>
|
||||
<FormField label='First Name'>
|
||||
<Field name='firstName' placeholder='First Name' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Last Name'>
|
||||
<Field name='lastName' placeholder='Last Name' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Phone Number'>
|
||||
<Field name='phoneNumber' placeholder='Phone Number' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='E-Mail'>
|
||||
<Field name='email' placeholder='E-Mail' disabled />
|
||||
</FormField>
|
||||
|
||||
<FormField label='App Role' labelFor='app_role'>
|
||||
<Field
|
||||
name='app_role'
|
||||
id='app_role'
|
||||
component={SelectField}
|
||||
options={initialValues.app_role}
|
||||
itemRef={'roles'}
|
||||
showField={'name'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Disabled' labelFor='disabled'>
|
||||
<Field
|
||||
name='disabled'
|
||||
id='disabled'
|
||||
component={SwitchField}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label="Password"
|
||||
>
|
||||
<Field
|
||||
name="password"
|
||||
placeholder="password"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/users/users-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type='reset'
|
||||
className='rounded-full border border-[#19192d]/10 bg-white px-4 py-2 text-sm font-semibold text-[#19192d]'
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
type='submit'
|
||||
className='rounded-full bg-[#35b7a5] px-4 py-2 text-sm font-semibold text-white'
|
||||
>
|
||||
Save profile
|
||||
</button>
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</Panel>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditUsers.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||
};
|
||||
|
||||
export default EditUsers;
|
||||
|
||||
@ -1,166 +1,201 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js'
|
||||
import Head from 'next/head'
|
||||
import axios from 'axios';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import CardBox from '../../components/CardBox'
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated'
|
||||
import SectionMain from '../../components/SectionMain'
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'
|
||||
import { getPageTitle } from '../../config'
|
||||
import TableUsers from '../../components/Users/TableUsers'
|
||||
import BaseButton from '../../components/BaseButton'
|
||||
import axios from "axios";
|
||||
import Link from "next/link";
|
||||
import {useAppDispatch, useAppSelector} from "../../stores/hooks";
|
||||
import CardBoxModal from "../../components/CardBoxModal";
|
||||
import DragDropFilePicker from "../../components/DragDropFilePicker";
|
||||
import {setRefetch, uploadCsv} from '../../stores/users/usersSlice';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import TableUsers from '../../components/Users/TableUsers';
|
||||
import { getPageTitle } from '../../config';
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { setRefetch, uploadCsv } from '../../stores/users/usersSlice';
|
||||
|
||||
function Panel({
|
||||
children,
|
||||
className = '',
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className={`rounded-lg border border-[#19192d]/10 bg-white ${className}`}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
import {hasPermission} from "../../helpers/userPermissions";
|
||||
function ActionButton({
|
||||
children,
|
||||
href,
|
||||
onClick,
|
||||
variant = 'primary',
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
variant?: 'primary' | 'secondary';
|
||||
}) {
|
||||
const className =
|
||||
variant === 'primary'
|
||||
? 'rounded-full bg-[#35b7a5] px-4 py-2 text-sm font-semibold text-white'
|
||||
: 'rounded-full border border-[#19192d]/10 bg-white px-4 py-2 text-sm font-semibold text-[#19192d]';
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<a href={href} className={className}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button type='button' className={className} onClick={onClick}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const UsersTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
||||
const [filters] = useState([{label: 'First Name', title: 'firstName'},{label: 'Last Name', title: 'lastName'},{label: 'Phone Number', title: 'phoneNumber'},{label: 'E-Mail', title: 'email'},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{label: 'App Role', title: 'app_role'},
|
||||
|
||||
|
||||
{label: 'Custom Permissions', title: 'custom_permissions'},
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'First Name', title: 'firstName' },
|
||||
{ label: 'Last Name', title: 'lastName' },
|
||||
{ label: 'Phone Number', title: 'phoneNumber' },
|
||||
{ label: 'E-Mail', title: 'email' },
|
||||
{ label: 'App Role', title: 'app_role' },
|
||||
{ label: 'Custom Permissions', title: 'custom_permissions' },
|
||||
]);
|
||||
|
||||
const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_USERS');
|
||||
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_USERS');
|
||||
|
||||
const getUsersCSV = async () => {
|
||||
const response = await axios({url: '/users?filetype=csv', method: 'GET',responseType: 'blob'});
|
||||
const type = response.headers['content-type']
|
||||
const blob = new Blob([response.data], { type: type })
|
||||
const link = document.createElement('a')
|
||||
link.href = window.URL.createObjectURL(blob)
|
||||
link.download = 'usersCSV.csv'
|
||||
link.click()
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: filters[0].title,
|
||||
},
|
||||
};
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
const getUsersCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/users?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'usersCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Users')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Users" main>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox id="usersList" className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
|
||||
{hasCreatePermission && <BaseButton className={'mr-3'} href={'/users/users-new'} color='info' label='Add/Invite User'/>}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton className={'mr-3'} color='info' label='Download CSV' onClick={getUsersCSV} />
|
||||
|
||||
|
||||
<main className='mx-auto max-w-7xl px-6 py-6'>
|
||||
<div className='mb-4 rounded-lg bg-[#19192d] p-5 text-white'>
|
||||
<div className='flex items-center gap-3 text-[#b17a1e]'>
|
||||
<span className='text-lg'>●</span>
|
||||
<span className='text-xs font-semibold uppercase tracking-[0.22em]'>
|
||||
Team access
|
||||
</span>
|
||||
</div>
|
||||
<h1 className='mt-3 text-xl font-semibold'>Users</h1>
|
||||
<p className='mt-2 max-w-2xl text-sm leading-6 text-[#fffdf9]'>
|
||||
Manage workspace members, roles, and export or import user records.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Panel className='mb-4 p-4'>
|
||||
<div className='flex flex-wrap items-center gap-3'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
<ActionButton href='/users/users-new'>Add user</ActionButton>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
</div>
|
||||
|
||||
</CardBox>
|
||||
|
||||
<CardBox className="mb-6" hasTable>
|
||||
<ActionButton variant='secondary' onClick={addFilter}>
|
||||
Filter
|
||||
</ActionButton>
|
||||
<ActionButton variant='secondary' onClick={getUsersCSV}>
|
||||
Download CSV
|
||||
</ActionButton>
|
||||
{hasCreatePermission && (
|
||||
<ActionButton
|
||||
variant='secondary'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
>
|
||||
Upload CSV
|
||||
</ActionButton>
|
||||
)}
|
||||
<div className='ms-auto' id='delete-rows-button'></div>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<Panel className='overflow-hidden'>
|
||||
<TableUsers
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={false}
|
||||
/>
|
||||
</CardBox>
|
||||
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</Panel>
|
||||
</main>
|
||||
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel='Confirm'
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats='.csv'
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
UsersTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated
|
||||
|
||||
permission={'READ_USERS'}
|
||||
|
||||
>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
)
|
||||
}
|
||||
<LayoutAuthenticated permission='READ_USERS'>{page}</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsersTablesPage
|
||||
export default UsersTablesPage;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user