Skip to content
Extraits de code Groupes Projets
Valider 71b41026 rédigé par Mohamed Lemine BAILLAHI's avatar Mohamed Lemine BAILLAHI
Parcourir les fichiers

Merge branch 'feature/HIR-26' into 'develop'

Création de l'interface de profile public et gestion administrative de la liste des utilisateurs

Closes HIR-2

See merge request !17
parents 2feb48d5 19b6d337
Branches
1 requête de fusion!17Création de l'interface de profile public et gestion administrative de la liste des utilisateurs
Pipeline #3667 réussi avec les étapes
in 3 minutes et 36 secondes
Affichage de
avec 1224 ajouts et 9 suppressions
import { CONFIG } from 'src/config-global';
import { ProfileViewPublic } from 'src/shared/sections/user/view';
// ----------------------------------------------------------------------
export const metadata = { title: `profile public | Dashboard - ${CONFIG.site.name}` };
export default function Page() {
return <ProfileViewPublic />;
}
\ No newline at end of file
import { CONFIG } from 'src/config-global';
import { UserListView } from 'src/shared/sections/user/view';
// ----------------------------------------------------------------------
export const metadata = { title: `User list | Dashboard - ${CONFIG.site.name}` };
export default function Page() {
return <UserListView />;
}
......@@ -25,9 +25,26 @@ export interface IUserProfileExperience {
jobTitle: string;
company: string;
period: string;
description: string; // Remplace tasks par description
contractType: 'freelance' | 'CDI' | 'CDD' | 'stage'; // Nouveau champ
}
description: string;
contractType: 'freelance' | 'CDI' | 'CDD' | 'stage';
}
export type IUserItem = {
id: string;
name: string;
city: string;
role: string;
email: string;
state: string;
status: string;
address: string;
country: string;
zipCode: string;
company: string;
avatarUrl: string;
phoneNumber: string;
isVerified: boolean;
};
export interface IUserProfileNft {
nftBadges: {
......@@ -37,6 +54,7 @@ export interface IUserProfileNft {
date: string;
}[];
}
export interface ITestItem {
id: string;
name: string;
......@@ -44,6 +62,7 @@ export interface ITestItem {
score: number;
date: string;
}
export interface Offer {
date: string | number | Date;
id: string;
......@@ -51,3 +70,9 @@ export interface Offer {
jobTitle: string;
description: string;
}
export type IUserTableFilters = {
name: string;
role: string[];
status: string;
};
\ No newline at end of file
......@@ -68,10 +68,17 @@ export const paths = {
new: `${ROOTS.DASHBOARD}/user/new`,
profile: `${ROOTS.DASHBOARD}/user/profile`,
account: `${ROOTS.DASHBOARD}/user/account`,
list: `${ROOTS.DASHBOARD}/user/list`,
exprience: `${ROOTS.DASHBOARD}/user/add-experience`,
edit: (id: string) => `${ROOTS.DASHBOARD}/user/${id}/edit`,
wallet: `${ROOTS.DASHBOARD}/user/wallet`,
},
public:
{ root: `${ROOTS.DASHBOARD}/public`,
profile: `${ROOTS.DASHBOARD}/public/profile`,
},
freelancers: {
root: `${ROOTS.FREELANCERS}`,
jobs: `${ROOTS.DASHBOARD}${ROOTS.FREELANCERS}/job`,
......
import type { ITestItem, IUserProfile, IUserProfileNft, IUserProfileExperience } from 'src/contexts/types/user';
import type { ITestItem, IUserItem, IUserProfile, IUserProfileNft, IUserProfileExperience } from 'src/contexts/types/user';
export const _USER_STATUS_OPTIONS = [
{ value: 'active', label: 'Active' },
{ value: 'bloque', label: 'Bloqué' },
];
export const id_user = ['0', '1', '2'];
// Renommer pour éviter le conflit
export const _USER_ROLES = [
{ value: 'candidat', label: 'Candidat' },
{ value: 'recruteur', label: 'Recruteur' },
{ value: 'freelance', label: 'Freelance' },
];
export const _userAbout: IUserProfile = {
role: 'software engineer junior',
......@@ -72,3 +85,54 @@ export const _userTests: ITestItem[] = [
date: '2024-06-20',
},
];
export const _userList: IUserItem[] = [
{
id: '1',
name: 'John Doe',
phoneNumber: '1234567890',
company: 'Company A',
role: 'candidat',
status: 'active',
email: 'john.doe@example.com',
avatarUrl: 'https://example.com/avatar1.jpg',
isVerified: true,
city: '',
state: '',
address: '',
country: '',
zipCode: '',
},
{
id: '1',
name: 'John jack',
phoneNumber: '1234567890',
company: 'Company A',
role: 'recruteur',
status: 'active',
email: 'john.jack@example.com',
avatarUrl: 'https://example.com/avatar1.jpg',
isVerified: true,
city: '',
state: '',
address: '',
country: '',
zipCode: '',
},
{
id: '2',
name: 'Jane Smith',
phoneNumber: '0987654321',
company: 'Company B',
role: 'freelance',
status: 'bloque',
email: 'jane.smith@example.com',
avatarUrl: 'https://example.com/avatar2.jpg',
isVerified: false,
city: '',
state: '',
address: '',
country: '',
zipCode: '',
},
];
\ No newline at end of file
......@@ -13,16 +13,19 @@ export function emptyRows(page: number, rowsPerPage: number, arrayLength: number
// ----------------------------------------------------------------------
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
if (a[orderBy] === null) {
const aValue = a[orderBy];
const bValue = b[orderBy];
if (aValue === null || aValue === undefined) {
return 1;
}
if (b[orderBy] === null) {
if (bValue === null || bValue === undefined) {
return -1;
}
if (b[orderBy] < a[orderBy]) {
if (bValue < aValue) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
if (bValue > aValue) {
return 1;
}
return 0;
......@@ -45,3 +48,4 @@ export function getComparator<Key extends keyof any>(
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
......@@ -51,6 +51,7 @@ export const navData = [
children: [
{ title: 'Profile', path: paths.dashboard.user.root },
{ title: 'Account', path: paths.dashboard.user.account },
{ title: 'liste', path: paths.dashboard.user.list}
],
},
......
'use client';
import {
Box,
Card,
Grid,
Link,
Stack,
CardHeader,
Typography,
CardContent,
} from '@mui/material';
import { _socials } from 'src/shared/_mock';
import { Iconify, SocialIcon } from 'src/shared/components/iconify';
interface Info {
role: string;
email: string;
school: string[] | null;
company: string;
country: string[] | null;
quote: string;
socialLinks: {
facebook: string;
instagram: string;
linkedin: string;
twitter: string;
};
}
interface Experience {
id: string | number;
jobTitle: string;
company: string;
period: string;
description: string;
contractType: string;
}
interface Props {
info: Info;
experiences: Experience[];
}
export function HomePublic({ info, experiences }: Props) {
const renderSocials = (
<Card>
<CardHeader title="Social" />
<Stack spacing={2} sx={{ p: 3 }}>
{_socials.map((link) => (
<Stack
key={link.name}
spacing={2}
direction="row"
sx={{ wordBreak: 'break-all', typography: 'body2' }}
>
<SocialIcon icon={link.value} />
<Link color="inherit" href={info.socialLinks[link.value as keyof Info['socialLinks']]}>
{link.value}
</Link>
</Stack>
))}
</Stack>
</Card>
);
const renderAbout = (
<Card>
<CardHeader title="About" />
<Stack spacing={2} sx={{ p: 3 }}>
<Box sx={{ typography: 'body2' }}>{info.quote}</Box>
<Stack direction="row" spacing={2}>
<Iconify icon="mingcute:location-fill" width={24} />
<Box sx={{ typography: 'body2' }}>
{`Live at `}
<Link variant="subtitle2" color="inherit">
{info.country?.join(', ')}
</Link>
</Box>
</Stack>
<Stack direction="row" sx={{ typography: 'body2' }}>
<Iconify icon="fluent:mail-24-filled" width={24} sx={{ mr: 2 }} />
{info.email}
</Stack>
<Stack direction="row" spacing={2}>
<Iconify icon="ic:round-business-center" width={24} />
<Box sx={{ typography: 'body2' }}>
{info.role} {`at `}
<Link variant="subtitle2" color="inherit">
{info.company}
</Link>
</Box>
</Stack>
<Stack direction="row" spacing={2}>
<Iconify icon="ic:round-school" width={24} />
<Box sx={{ typography: 'body2' }}>
{`Studied at `}
<Link variant="subtitle2" color="inherit">
{info.school?.join(', ')}
</Link>
</Box>
</Stack>
</Stack>
</Card>
);
return (
<Box sx={{ display: 'flex', gap: 4 }}>
<Grid item xs={12} md={4}>
<Stack spacing={3}>
{renderAbout}
{renderSocials}
</Stack>
</Grid>
<Box sx={{ flex: 1 }}>
<Box sx={{ mb: 4 }}>
<Typography variant="h6">Experiences</Typography>
<Stack spacing={3}>
{experiences.length > 0 ? (
experiences.map((exp) => (
<Card key={exp.id}>
<CardHeader
title={exp.jobTitle}
subheader={
<Box component="span">
<Typography component="span" variant="body2" sx={{ mr: 1 }}>
{exp.company}
</Typography>
<Typography component="span" variant="body2">
{exp.period}
</Typography>
</Box>
}
/>
<CardContent>
<Typography variant="body2">{exp.contractType}</Typography>
<Typography variant="body2" sx={{ mt: 1 }}>
{exp.description}
</Typography>
</CardContent>
</Card>
))
) : (
<Typography variant="body1">No experiences available</Typography>
)}
</Stack>
</Box>
</Box>
</Box>
);
}
import type { IUserItem } from 'src/types/user';
import { z as zod } from 'zod';
import { useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { isValidPhoneNumber } from 'react-phone-number-input/input';
import Box from '@mui/material/Box';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import MenuItem from '@mui/material/MenuItem';
import LoadingButton from '@mui/lab/LoadingButton';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import { _USER_STATUS_OPTIONS } from 'src/shared/_mock/_user';
import { toast } from 'src/shared/components/snackbar';
import { Form, Field, schemaHelper } from 'src/shared/components/hook-form';
// ----------------------------------------------------------------------
export type UserQuickEditSchemaType = zod.infer<typeof UserQuickEditSchema>;
export const UserQuickEditSchema = zod.object({
name: zod.string().min(1, { message: 'Name is required!' }),
email: zod
.string()
.min(1, { message: 'Email is required!' })
.email({ message: 'Email must be a valid email address!' }),
phoneNumber: schemaHelper.phoneNumber({ isValidPhoneNumber }),
country: schemaHelper.objectOrNull<string | null>({
message: { required_error: 'Country is required!' },
}),
state: zod.string().min(1, { message: 'State is required!' }),
city: zod.string().min(1, { message: 'City is required!' }),
address: zod.string().min(1, { message: 'Address is required!' }),
zipCode: zod.string().min(1, { message: 'Zip code is required!' }),
company: zod.string().min(1, { message: 'Company is required!' }),
role: zod.string().min(1, { message: 'Role is required!' }),
// Not required
status: zod.string(),
});
// ----------------------------------------------------------------------
type Props = {
open: boolean;
onClose: () => void;
currentUser?: IUserItem;
};
export function UserQuickEditForm({ currentUser, open, onClose }: Props) {
const defaultValues = useMemo(
() => ({
name: currentUser?.name || '',
email: currentUser?.email || '',
phoneNumber: currentUser?.phoneNumber || '',
address: currentUser?.address || '',
country: currentUser?.country || '',
state: currentUser?.state || '',
city: currentUser?.city || '',
zipCode: currentUser?.zipCode || '',
status: currentUser?.status,
company: currentUser?.company || '',
role: currentUser?.role || '',
}),
[currentUser]
);
const methods = useForm<UserQuickEditSchemaType>({
mode: 'all',
resolver: zodResolver(UserQuickEditSchema),
defaultValues,
});
const {
reset,
handleSubmit,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data) => {
const promise = new Promise((resolve) => setTimeout(resolve, 1000));
try {
reset();
onClose();
toast.promise(promise, {
loading: 'Loading...',
success: 'Update success!',
error: 'Update error!',
});
await promise;
console.info('DATA', data);
} catch (error) {
console.error(error);
}
});
return (
<Dialog
fullWidth
maxWidth={false}
open={open}
onClose={onClose}
PaperProps={{ sx: { maxWidth: 720 } }}
>
<Form methods={methods} onSubmit={onSubmit}>
<DialogTitle>Quick Update</DialogTitle>
<DialogContent>
<Alert variant="outlined" severity="info" sx={{ mb: 3 }}>
Account is waiting for confirmation
</Alert>
<Box
rowGap={3}
columnGap={2}
display="grid"
gridTemplateColumns={{ xs: 'repeat(1, 1fr)', sm: 'repeat(2, 1fr)' }}
>
<Field.Select name="status" label="Status">
{_USER_STATUS_OPTIONS.map((status) => (
<MenuItem key={status.value} value={status.value}>
{status.label}
</MenuItem>
))}
</Field.Select>
<Box sx={{ display: { xs: 'none', sm: 'block' } }} />
<Field.Text name="name" label="Full name" />
<Field.Text name="email" label="Email address" />
<Field.Phone name="phoneNumber" label="Phone number" />
<Field.CountrySelect
fullWidth
name="country"
label="Country"
placeholder="Choose a country"
/>
<Field.Text name="state" label="State/region" />
<Field.Text name="city" label="City" />
<Field.Text name="address" label="Address" />
<Field.Text name="zipCode" label="Zip/code" />
<Field.Text name="company" label="Company" />
<Field.Text name="role" label="Role" />
</Box>
</DialogContent>
<DialogActions>
<Button variant="outlined" onClick={onClose}>
Cancel
</Button>
<LoadingButton type="submit" variant="contained" loading={isSubmitting}>
Update
</LoadingButton>
</DialogActions>
</Form>
</Dialog>
);
}
\ No newline at end of file
import type { IUserTableFilters } from 'src/types/user';
import type { Theme, SxProps } from '@mui/material/styles';
import type { UseSetStateReturn } from 'src/hooks/use-set-state';
import { useCallback } from 'react';
import Chip from '@mui/material/Chip';
import { chipProps, FiltersBlock, FiltersResult } from 'src/shared/components/filters-result';
// ----------------------------------------------------------------------
type Props = {
totalResults: number;
sx?: SxProps<Theme>;
onResetPage: () => void;
filters: UseSetStateReturn<IUserTableFilters>;
};
export function UserTableFiltersResult({ filters, onResetPage, totalResults, sx }: Props) {
const handleRemoveKeyword = useCallback(() => {
onResetPage();
filters.setState({ name: '' });
}, [filters, onResetPage]);
const handleRemoveStatus = useCallback(() => {
onResetPage();
filters.setState({ status: 'all' });
}, [filters, onResetPage]);
const handleRemoveRole = useCallback(
(inputValue: string) => {
const newValue = filters.state.role.filter((item) => item !== inputValue);
onResetPage();
filters.setState({ role: newValue });
},
[filters, onResetPage]
);
const handleReset = useCallback(() => {
onResetPage();
filters.onResetState();
}, [filters, onResetPage]);
return (
<FiltersResult totalResults={totalResults} onReset={handleReset} sx={sx}>
<FiltersBlock label="Status:" isShow={filters.state.status !== 'all'}>
<Chip
{...chipProps}
label={filters.state.status}
onDelete={handleRemoveStatus}
sx={{ textTransform: 'capitalize' }}
/>
</FiltersBlock>
<FiltersBlock label="Role:" isShow={!!filters.state.role.length}>
{filters.state.role.map((item) => (
<Chip {...chipProps} key={item} label={item} onDelete={() => handleRemoveRole(item)} />
))}
</FiltersBlock>
<FiltersBlock label="Keyword:" isShow={!!filters.state.name}>
<Chip {...chipProps} label={filters.state.name} onDelete={handleRemoveKeyword} />
</FiltersBlock>
</FiltersResult>
);
}
\ No newline at end of file
import type { IUserItem } from 'src/types/user';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Button from '@mui/material/Button';
import Avatar from '@mui/material/Avatar';
import Tooltip from '@mui/material/Tooltip';
import MenuList from '@mui/material/MenuList';
import MenuItem from '@mui/material/MenuItem';
import TableRow from '@mui/material/TableRow';
import Checkbox from '@mui/material/Checkbox';
import TableCell from '@mui/material/TableCell';
import IconButton from '@mui/material/IconButton';
import { useBoolean } from 'src/hooks/use-boolean';
import { Label } from 'src/shared/components/label';
import { Iconify } from 'src/shared/components/iconify';
import { ConfirmDialog } from 'src/shared/components/custom-dialog';
import { usePopover, CustomPopover } from 'src/shared/components/custom-popover';
import { UserQuickEditForm } from './user-quick-edit-form';
// ----------------------------------------------------------------------
type Props = {
row: IUserItem;
selected: boolean;
onEditRow: () => void;
onSelectRow: () => void;
onDeleteRow: () => void;
};
export function UserTableRow({ row, selected, onEditRow, onSelectRow, onDeleteRow }: Props) {
const confirm = useBoolean();
const popover = usePopover();
const quickEdit = useBoolean();
return (
<>
<TableRow hover selected={selected} aria-checked={selected} tabIndex={-1}>
<TableCell padding="checkbox">
<Checkbox id={row.id} checked={selected} onClick={onSelectRow} />
</TableCell>
<TableCell>
<Stack spacing={2} direction="row" alignItems="center">
<Avatar alt={row.name} src={row.avatarUrl} />
<Stack sx={{ typography: 'body2', flex: '1 1 auto', alignItems: 'flex-start' }}>
<Link color="inherit" onClick={onEditRow} sx={{ cursor: 'pointer' }}>
{row.name}
</Link>
</Stack>
</Stack>
</TableCell>
<TableCell sx={{ whiteSpace: 'nowrap' }}>{row.phoneNumber}</TableCell>
<TableCell sx={{ whiteSpace: 'nowrap' }}>{row.email}</TableCell>
<TableCell sx={{ whiteSpace: 'nowrap' }}>{row.role}</TableCell>
<TableCell>
<Label
variant="soft"
color={
(row.status === 'active' && 'success') ||
(row.status === 'bloque' && 'warning') ||
'default'
}
>
{row.status}
</Label>
</TableCell>
<TableCell>
<Stack direction="row" alignItems="center">
<Tooltip title="Quick Edit" placement="top" arrow>
<IconButton
color={quickEdit.value ? 'inherit' : 'default'}
onClick={quickEdit.onTrue}
>
<Iconify icon="solar:pen-bold" />
</IconButton>
</Tooltip>
<IconButton color={popover.open ? 'inherit' : 'default'} onClick={popover.onOpen}>
<Iconify icon="eva:more-vertical-fill" />
</IconButton>
</Stack>
</TableCell>
</TableRow>
<UserQuickEditForm currentUser={row} open={quickEdit.value} onClose={quickEdit.onFalse} />
<CustomPopover
open={popover.open}
anchorEl={popover.anchorEl}
onClose={popover.onClose}
slotProps={{ arrow: { placement: 'right-top' } }}
>
<MenuList>
<MenuItem
onClick={() => {
confirm.onTrue();
popover.onClose();
}}
sx={{ color: 'error.main' }}
>
<Iconify icon="solar:trash-bin-trash-bold" />
Delete
</MenuItem>
<MenuItem
onClick={() => {
onEditRow();
popover.onClose();
}}
>
<Iconify icon="mdi:block-helper" />
Bloque
</MenuItem>
</MenuList>
</CustomPopover>
<ConfirmDialog
open={confirm.value}
onClose={confirm.onFalse}
title="Delete"
content="Are you sure want to delete?"
action={
<Button variant="contained" color="error" onClick={onDeleteRow}>
Delete
</Button>
}
/>
</>
);
}
import type { IUserTableFilters } from 'src/types/user';
import type { SelectChangeEvent } from '@mui/material/Select';
import type { UseSetStateReturn } from 'src/hooks/use-set-state';
import { useCallback } from 'react';
import Stack from '@mui/material/Stack';
import Select from '@mui/material/Select';
import MenuList from '@mui/material/MenuList';
import MenuItem from '@mui/material/MenuItem';
import Checkbox from '@mui/material/Checkbox';
import TextField from '@mui/material/TextField';
import InputLabel from '@mui/material/InputLabel';
import IconButton from '@mui/material/IconButton';
import FormControl from '@mui/material/FormControl';
import OutlinedInput from '@mui/material/OutlinedInput';
import InputAdornment from '@mui/material/InputAdornment';
import { Iconify } from 'src/shared/components/iconify';
import { usePopover, CustomPopover } from 'src/shared/components/custom-popover';
// ----------------------------------------------------------------------
type Props = {
onResetPage: () => void;
filters: UseSetStateReturn<IUserTableFilters>;
options: {
roles: string[];
};
};
export function UserTableToolbar({ filters, options, onResetPage }: Props) {
const popover = usePopover();
const handleFilterName = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
onResetPage();
filters.setState({ name: event.target.value });
},
[filters, onResetPage]
);
const handleFilterRole = useCallback(
(event: SelectChangeEvent<string[]>) => {
const newValue =
typeof event.target.value === 'string' ? event.target.value.split(',') : event.target.value;
onResetPage();
filters.setState({ role: newValue });
},
[filters, onResetPage]
);
return (
<>
<Stack
spacing={2}
alignItems={{ xs: 'flex-end', md: 'center' }}
direction={{ xs: 'column', md: 'row' }}
sx={{ p: 2.5, pr: { xs: 2.5, md: 1 } }}
>
<FormControl sx={{ flexShrink: 0, width: { xs: 1, md: 200 } }}>
<InputLabel htmlFor="user-filter-role-select-label">Role</InputLabel>
<Select
multiple
value={filters.state.role}
onChange={handleFilterRole}
input={<OutlinedInput label="Role" />}
renderValue={(selected) => selected.map((value) => value).join(', ')}
inputProps={{ id: 'user-filter-role-select-label' }}
MenuProps={{ PaperProps: { sx: { maxHeight: 240 } } }}
>
{options.roles.map((option) => (
<MenuItem key={option} value={option}>
<Checkbox
disableRipple
size="small"
checked={filters.state.role.includes(option)}
/>
{option}
</MenuItem>
))}
</Select>
</FormControl>
<Stack direction="row" alignItems="center" spacing={2} flexGrow={1} sx={{ width: 1 }}>
<TextField
fullWidth
value={filters.state.name}
onChange={handleFilterName}
placeholder="Search..."
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Iconify icon="eva:search-fill" sx={{ color: 'text.disabled' }} />
</InputAdornment>
),
}}
/>
<IconButton onClick={popover.onOpen}>
<Iconify icon="eva:more-vertical-fill" />
</IconButton>
</Stack>
</Stack>
<CustomPopover
open={popover.open}
anchorEl={popover.anchorEl}
onClose={popover.onClose}
slotProps={{ arrow: { placement: 'right-top' } }}
>
<MenuList>
<MenuItem
onClick={() => {
popover.onClose();
}}
>
<Iconify icon="solar:printer-minimalistic-bold" />
Print
</MenuItem>
<MenuItem
onClick={() => {
popover.onClose();
}}
>
<Iconify icon="solar:import-bold" />
Import
</MenuItem>
<MenuItem
onClick={() => {
popover.onClose();
}}
>
<Iconify icon="solar:export-bold" />
Export
</MenuItem>
</MenuList>
</CustomPopover>
</>
);
}
\ No newline at end of file
export * from './user-list-view';
export * from './liste-badge-nft';
export * from './user-profile-view';
export * from './profile-view-public.';
export * from './Experience-create-view';
'use client';
import Box from '@mui/material/Box';
import Tab from '@mui/material/Tab';
import Card from '@mui/material/Card';
import Tabs from '@mui/material/Tabs';
import { useTabs } from 'src/hooks/use-tabs';
import { _userAbout } from 'src/shared/_mock/_user';
import { DashboardContent } from 'src/shared/layouts/dashboard';
import { _userExperiences } from 'src/shared/_mock/_userExperiences';
import { Iconify } from 'src/shared/components/iconify';
import { useMockedUser } from 'src/auth/hooks';
import { HomePublic } from '../public-home';
import { ListeBadge } from './liste-badge-nft';
import { ProfileCover } from '../profile-cover';
// ----------------------------------------------------------------------
const TABS = [
{ value: 'profile', label: 'Profile', icon: <Iconify icon="solar:user-id-bold" width={24} /> },
{ value: 'listes-des-badges-nft', label: 'Listes des Badges NFT', icon: <Iconify icon="mdi:medal" width={24} /> },
];
// ----------------------------------------------------------------------
export function ProfileViewPublic() {
const { user } = useMockedUser();
const tabs = useTabs('profile');
return (
<DashboardContent>
<Card sx={{ mb: 3, height: 290, position: 'relative' }}>
<ProfileCover
role={_userAbout.role}
name={user?.displayName || 'Unknown User'}
avatarUrl={user?.photoURL || ''}
coverUrl=""
/>
<Box
display="flex"
justifyContent={{ xs: 'center', md: 'flex-end' }}
sx={{
width: 1,
bottom: 0,
zIndex: 9,
px: { md: 3 },
position: 'absolute',
bgcolor: 'background.paper',
}}
>
<Tabs value={tabs.value} onChange={tabs.onChange}>
{TABS.map((tab) => (
<Tab key={tab.value} value={tab.value} icon={tab.icon} label={tab.label} />
))}
</Tabs>
</Box>
</Card>
{tabs.value === 'profile' && (
<HomePublic
info={_userAbout}
experiences={_userExperiences}
/>
)}
{tabs.value === 'listes-des-badges-nft' && <ListeBadge />}
</DashboardContent>
);
}
'use client';
import type { IUserItem, IUserTableFilters } from 'src/contexts/types/user';
import { useState, useCallback } from 'react';
import Box from '@mui/material/Box';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import Card from '@mui/material/Card';
import Table from '@mui/material/Table';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import TableBody from '@mui/material/TableBody';
import IconButton from '@mui/material/IconButton';
import { paths } from 'src/routes/paths';
import { useRouter } from 'src/routes/hooks';
import { useBoolean } from 'src/hooks/use-boolean';
import { useSetState } from 'src/hooks/use-set-state';
import { varAlpha } from 'src/shared/theme/styles';
import { DashboardContent } from 'src/shared/layouts/dashboard';
import { _userList, _USER_ROLES, _USER_STATUS_OPTIONS } from 'src/shared/_mock/_user';
import { Label } from 'src/shared/components/label';
import { toast } from 'src/shared/components/snackbar';
import { Iconify } from 'src/shared/components/iconify';
import { Scrollbar } from 'src/shared/components/scrollbar';
import { ConfirmDialog } from 'src/shared/components/custom-dialog';
import { CustomBreadcrumbs } from 'src/shared/components/custom-breadcrumbs';
import {
useTable,
emptyRows,
rowInPage,
TableNoData,
getComparator,
TableEmptyRows,
TableHeadCustom,
TableSelectedAction,
TablePaginationCustom,
} from 'src/shared/components/table';
import { UserTableRow } from '../user-table-row';
import { UserTableToolbar } from '../user-table-toolbar';
import { UserTableFiltersResult } from '../user-table-filters-result';
// ----------------------------------------------------------------------
const STATUS_OPTIONS = [{ value: 'all', label: 'All' }, ..._USER_STATUS_OPTIONS];
const TABLE_HEAD = [
{ id: 'name', label: 'Name' },
{ id: 'phoneNumber', label: 'Phone number', width: 180 },
{ id: 'email', label: 'email', width: 220 },
{ id: 'role', label: 'Role', width: 180 },
{ id: 'status', label: 'Status', width: 100 },
{ id: '', width: 88 },
];
// ----------------------------------------------------------------------
export function UserListView() {
const table = useTable();
const router = useRouter();
const confirm = useBoolean();
const [tableData, setTableData] = useState<IUserItem[]>(_userList);
const filters = useSetState<IUserTableFilters>({ name: '', role: [], status: 'all' });
const dataFiltered = applyFilter({
inputData: tableData,
comparator: getComparator(table.order, table.orderBy),
filters: filters.state,
});
const dataInPage = rowInPage(dataFiltered, table.page, table.rowsPerPage);
const canReset =
!!filters.state.name || filters.state.role.length > 0 || filters.state.status !== 'all';
const notFound = (!dataFiltered.length && canReset) || !dataFiltered.length;
const handleDeleteRow = useCallback(
(id: string) => {
const deleteRow = tableData.filter((row) => row.id !== id);
toast.success('Delete success!');
setTableData(deleteRow);
table.onUpdatePageDeleteRow(dataInPage.length);
},
[dataInPage.length, table, tableData]
);
const handleDeleteRows = useCallback(() => {
const deleteRows = tableData.filter((row) => !table.selected.includes(row.id));
toast.success('Delete success!');
setTableData(deleteRows);
table.onUpdatePageDeleteRows({
totalRowsInPage: dataInPage.length,
totalRowsFiltered: dataFiltered.length,
});
}, [dataFiltered.length, dataInPage.length, table, tableData]);
const handleEditRow = useCallback(
(id: string) => {
router.push(paths.dashboard.public.profile);
},
[router]
);
const handleFilterStatus = useCallback(
(event: React.SyntheticEvent, newValue: string) => {
table.onResetPage();
filters.setState({ status: newValue });
},
[filters, table]
);
return (
<>
<DashboardContent>
<CustomBreadcrumbs
heading="List"
links={[
{ name: 'Dashboard', href: paths.dashboard.root },
{ name: 'User', href: paths.dashboard.user.root },
{ name: 'List' },
]}
/>
<Card>
<Tabs
value={filters.state.status}
onChange={handleFilterStatus}
sx={{
px: 2.5,
boxShadow: (theme) =>
`inset 0 -2px 0 0 ${varAlpha(theme.vars.palette.grey['500Channel'], 0.08)}`,
}}
>
{STATUS_OPTIONS.map((tab) => (
<Tab
key={tab.value}
iconPosition="end"
value={tab.value}
label={tab.label}
icon={
<Label
variant={
((tab.value === 'all' || tab.value === filters.state.status) && 'filled') ||
'soft'
}
color={
(tab.value === 'active' && 'success') ||
(tab.value === 'pending' && 'warning') ||
(tab.value === 'banned' && 'error') ||
'default'
}
>
{['active', 'pending', 'banned', 'rejected'].includes(tab.value)
? tableData.filter((user) => user.status === tab.value).length
: tableData.length}
</Label>
}
/>
))}
</Tabs>
<UserTableToolbar
filters={filters}
onResetPage={table.onResetPage}
options={{ roles: _USER_ROLES.map(role => role.value) }} // Transformation du tableau d'objets en tableau de strings
/>
{canReset && (
<UserTableFiltersResult
filters={filters}
totalResults={dataFiltered.length}
onResetPage={table.onResetPage}
sx={{ p: 2.5, pt: 0 }}
/>
)}
<Box sx={{ position: 'relative' }}>
<TableSelectedAction
dense={table.dense}
numSelected={table.selected.length}
rowCount={dataFiltered.length}
onSelectAllRows={(checked) =>
table.onSelectAllRows(
checked,
dataFiltered.map((row) => row.id)
)
}
action={
<Tooltip title="Delete">
<IconButton color="primary" onClick={confirm.onTrue}>
<Iconify icon="solar:trash-bin-trash-bold" />
</IconButton>
</Tooltip>
}
/>
<Scrollbar>
<Table size={table.dense ? 'small' : 'medium'} sx={{ minWidth: 960 }}>
<TableHeadCustom
order={table.order}
orderBy={table.orderBy}
headLabel={TABLE_HEAD}
rowCount={dataFiltered.length}
numSelected={table.selected.length}
onSort={table.onSort}
onSelectAllRows={(checked) =>
table.onSelectAllRows(
checked,
dataFiltered.map((row) => row.id)
)
}
/>
<TableBody>
{dataFiltered
.slice(
table.page * table.rowsPerPage,
table.page * table.rowsPerPage + table.rowsPerPage
)
.map((row) => (
<UserTableRow
key={row.id}
row={row as IUserItem}
selected={table.selected.includes(row.id)}
onSelectRow={() => table.onSelectRow(row.id)}
onDeleteRow={() => handleDeleteRow(row.id)}
onEditRow={() => handleEditRow(row.id)}
/>
))}
<TableEmptyRows
height={table.dense ? 56 : 56 + 20}
emptyRows={emptyRows(table.page, table.rowsPerPage, dataFiltered.length)}
/>
<TableNoData notFound={notFound} />
</TableBody>
</Table>
</Scrollbar>
</Box>
<TablePaginationCustom
page={table.page}
dense={table.dense}
count={dataFiltered.length}
rowsPerPage={table.rowsPerPage}
onPageChange={table.onChangePage}
onChangeDense={table.onChangeDense}
onRowsPerPageChange={table.onChangeRowsPerPage}
/>
</Card>
</DashboardContent>
<ConfirmDialog
open={confirm.value}
onClose={confirm.onFalse}
title="Delete"
content={
<>
Are you sure want to delete <strong> {table.selected.length} </strong> items?
</>
}
action={
<Button
variant="contained"
color="error"
onClick={() => {
handleDeleteRows();
confirm.onFalse();
}}
>
Delete
</Button>
}
/>
</>
);
}
// ----------------------------------------------------------------------
type ApplyFilterProps = {
inputData: IUserItem[];
filters: IUserTableFilters;
comparator: (a: any, b: any) => number;
};
function applyFilter({ inputData, comparator, filters }: ApplyFilterProps) {
const { name, status, role } = filters;
const stabilizedThis = inputData.map((el, index) => [el, index] as const);
stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) return order;
return a[1] - b[1];
});
inputData = stabilizedThis.map((el) => el[0]);
if (name) {
inputData = inputData.filter(
(user) => user.name.toLowerCase().indexOf(name.toLowerCase()) !== -1
);
}
if (status !== 'all') {
inputData = inputData.filter((user) => user.status === status);
}
if (role.length) {
inputData = inputData.filter((user) => role.includes(user.role));
}
return inputData;
}
\ No newline at end of file
0% ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter