hide notification dashboard & add search employee data
This commit is contained in:
@@ -203,6 +203,12 @@ class CorporateMemberService
|
||||
->whereHas('employeds', function ($query) use ($corporateId) {
|
||||
$query->where('corporate_id', $corporateId);
|
||||
})
|
||||
->when($request->input('search'), function ($query, $search) {
|
||||
$query->where(function ($query) use ($search) {
|
||||
$query->orWhere('member_id', 'like', "%" . $search . "%")
|
||||
->orWhere('name', 'like', "%" . $search . "%");
|
||||
});
|
||||
})
|
||||
->when($request->has('orderBy'), function ($query) use ($request) {
|
||||
$orderBy = match ($request->input('orderBy')) {
|
||||
'memberId' => 'member_id',
|
||||
|
||||
@@ -84,6 +84,7 @@ export type TableListProps<DataType> = {
|
||||
};
|
||||
searchs?: {
|
||||
useSearchs: boolean;
|
||||
fullWidth?: boolean;
|
||||
searchText: string;
|
||||
setSearchText: Dispatch<SetStateAction<string>>;
|
||||
handleSearchSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* ---------------------------------- @mui ---------------------------------- */
|
||||
import { styled } from '@mui/material/styles';
|
||||
import {
|
||||
Paper,
|
||||
Table as TableContent,
|
||||
@@ -12,49 +11,23 @@ import {
|
||||
Button,
|
||||
TableSortLabel,
|
||||
Box,
|
||||
Card,
|
||||
Grid,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
SelectChangeEvent,
|
||||
Stack,
|
||||
InputAdornment,
|
||||
Typography,
|
||||
LinearProgress,
|
||||
linearProgressClasses,
|
||||
} from '@mui/material';
|
||||
import { visuallyHidden } from '@mui/utils';
|
||||
/* ---------------------------------- axios --------------------------------- */
|
||||
import axios from '../utils/axios';
|
||||
/* ---------------------------------- react --------------------------------- */
|
||||
import { Fragment, useContext, useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Fragment } from 'react';
|
||||
/* -------------------------------- component ------------------------------- */
|
||||
import BaseTablePagination from './BaseTablePagination';
|
||||
/* ---------------------------------- theme --------------------------------- */
|
||||
import palette from '../theme/palette';
|
||||
/* ---------------------------------- utils --------------------------------- */
|
||||
import { UserCurrentCorporateContext } from '../contexts/UserCurrentCorporate';
|
||||
import { fSplit } from '../utils/formatNumber';
|
||||
import { Download, Search as SearchIcon, Upload } from '@mui/icons-material';
|
||||
import { Download, Search as SearchIcon } from '@mui/icons-material';
|
||||
/* ---------------------------------- types --------------------------------- */
|
||||
import { DivisionDataProps, Order, PaginationTableProps, TableListProps } from '../@types/table';
|
||||
import { InputAdornment } from '@mui/material';
|
||||
import GetAppIcon from '@mui/icons-material/GetApp';
|
||||
/* --------------------------------- styled --------------------------------- */
|
||||
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
|
||||
height: 10,
|
||||
borderRadius: 6,
|
||||
[`&.${linearProgressClasses.colorPrimary}`]: {
|
||||
backgroundColor: '#D1F1F1',
|
||||
},
|
||||
[`& .${linearProgressClasses.bar}`]: {
|
||||
borderRadius: 6,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
}));
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import { DivisionDataProps, StatusDataProps, TableListProps } from '../@types/table';
|
||||
|
||||
export default function Table<T>({
|
||||
headCells,
|
||||
@@ -136,7 +109,7 @@ export default function Table<T>({
|
||||
const parameters = Object.fromEntries([
|
||||
...params.searchParams.entries(),
|
||||
['page', newPage + 1],
|
||||
['per_page', paginations.rowsPerPage]
|
||||
['per_page', paginations.rowsPerPage],
|
||||
]);
|
||||
paginations.setPage(newPage);
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
@@ -161,96 +134,95 @@ export default function Table<T>({
|
||||
|
||||
return (
|
||||
// <Card>
|
||||
<Grid container>
|
||||
{/* Field 1 */}
|
||||
<Grid item xs={12} paddingX="24px" paddingY="20px">
|
||||
<Grid container spacing={2}>
|
||||
{filters && filters.useFilter ? (
|
||||
<Fragment>
|
||||
<Grid item xs={12} lg={3} xl={2}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="simple-division-select-lable">Division</InputLabel>
|
||||
<Select
|
||||
labelId="simple-division-select-lable"
|
||||
id="division-select-lable"
|
||||
value={filters.config.divisionValue}
|
||||
label="Division"
|
||||
onChange={filters.config.handleDivisionChange}
|
||||
>
|
||||
<MenuItem value="all">All</MenuItem>
|
||||
{filters.config.divisionData.map((row: DivisionDataProps, index) => (
|
||||
<MenuItem key={index} value={row.id}>
|
||||
{row.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={9} xl={10}>
|
||||
<Grid container>
|
||||
{/* Field 1 */}
|
||||
<Grid item xs={12} paddingX="24px" paddingY="20px">
|
||||
<Grid container spacing={2}>
|
||||
{filters && filters.useFilter ? (
|
||||
<Fragment>
|
||||
<Grid item xs={12} lg={3} xl={2}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="simple-division-select-lable">Division</InputLabel>
|
||||
<Select
|
||||
labelId="simple-division-select-lable"
|
||||
id="division-select-lable"
|
||||
value={filters.config.divisionValue}
|
||||
label="Division"
|
||||
onChange={filters.config.handleDivisionChange}
|
||||
>
|
||||
<MenuItem value="all">All</MenuItem>
|
||||
{filters.config.divisionData.map((row: DivisionDataProps, index) => (
|
||||
<MenuItem key={index} value={row.id}>
|
||||
{row.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={9} xl={10}>
|
||||
<form onSubmit={searchs?.handleSearchSubmit}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
onChange={(event) => searchs?.setSearchText(event.target.value)}
|
||||
value={searchs?.searchText}
|
||||
fullWidth
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
||||
{searchs && searchs.useSearchs ? (
|
||||
<Fragment>
|
||||
{filterStatus && filterStatus.useFilter ? (
|
||||
<Grid item xs={12} lg={4} xl={4}>
|
||||
<form onSubmit={searchs.handleSearchSubmit}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
onChange={(event) => searchs.setSearchText(event.target.value)}
|
||||
value={searchs.searchText}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
placeholder="Search Name or Member ID... "
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
) : null }
|
||||
|
||||
{searchs && searchs.useSearchs ? (
|
||||
<Fragment>
|
||||
{filterStatus && filterStatus.useFilter ? (
|
||||
<Grid item xs={12} lg={4} xl={4}>
|
||||
<form onSubmit={searchs.handleSearchSubmit}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
variant="outlined"
|
||||
onChange={(event) => searchs.setSearchText(event.target.value)}
|
||||
value={searchs.searchText}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
placeholder="Search Name or Member ID... "
|
||||
/>
|
||||
</form>
|
||||
) : (
|
||||
<Grid item xs={12} lg={searchs.fullWidth ? 12 : 6} xl={searchs.fullWidth ? 12 : 6}>
|
||||
<form onSubmit={searchs.handleSearchSubmit}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
variant="outlined"
|
||||
onChange={(event) => searchs.setSearchText(event.target.value)}
|
||||
value={searchs.searchText}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
placeholder="Search Name or Member ID... "
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
) :
|
||||
<Grid item xs={12} lg={6} xl={6}>
|
||||
<form onSubmit={searchs.handleSearchSubmit}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
variant="outlined"
|
||||
onChange={(event) => searchs.setSearchText(event.target.value)}
|
||||
value={searchs.searchText}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
placeholder="Search Name or Member ID... "
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
}
|
||||
|
||||
</Fragment>
|
||||
) : null }
|
||||
)}
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
||||
{/* Start date */}
|
||||
{filterStartDate && filterStartDate.useFilter ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
{/* Start date */}
|
||||
{filterStartDate && filterStartDate.useFilter ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
<form onChange={filterStartDate.handleStartDateChange}>
|
||||
<TextField
|
||||
id="date-input"
|
||||
@@ -266,12 +238,12 @@ export default function Table<T>({
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
) : null }
|
||||
) : null}
|
||||
|
||||
{/* End Date */}
|
||||
{/* End Date */}
|
||||
|
||||
{filterEndDate && filterEndDate.useFilter ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
{filterEndDate && filterEndDate.useFilter ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
<form onChange={filterEndDate.handleEndDateChange}>
|
||||
<TextField
|
||||
id="date-input"
|
||||
@@ -287,101 +259,105 @@ export default function Table<T>({
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
) : null }
|
||||
) : null}
|
||||
|
||||
{/* Filter status */}
|
||||
{filterStatus && filterStatus.useFilter ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="simple-status-select-lable">Status</InputLabel>
|
||||
<Select
|
||||
labelId="simple-status-select-lable"
|
||||
id="status-select-lable"
|
||||
value={filterStatus.config.statusValue}
|
||||
label="Status"
|
||||
onChange={filterStatus.config.handleStatusChange}
|
||||
>
|
||||
<MenuItem value="all">All</MenuItem>
|
||||
{filterStatus.config.filterData.map((row: StatusDataProps, index) => (
|
||||
<MenuItem key={index} value={row.id}>
|
||||
{row.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
) : null }
|
||||
{/* Filter status */}
|
||||
{filterStatus && filterStatus.useFilter ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="simple-status-select-lable">Status</InputLabel>
|
||||
<Select
|
||||
labelId="simple-status-select-lable"
|
||||
id="status-select-lable"
|
||||
value={filterStatus.config.statusValue}
|
||||
label="Status"
|
||||
onChange={filterStatus.config.handleStatusChange}
|
||||
>
|
||||
<MenuItem value="all">All</MenuItem>
|
||||
{filterStatus.config.statusData.map((row: StatusDataProps, index) => (
|
||||
<MenuItem key={index} value={row.id}>
|
||||
{row.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
) : null}
|
||||
|
||||
{/* Export Report */}
|
||||
|
||||
{exportReport && exportReport.useExport ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
<FormControl fullWidth>
|
||||
<Button variant='contained' sx={{p:2}} onClick={exportReport.handleExportReport}>
|
||||
<Download />
|
||||
<Typography variant='inherit' sx={{marginLeft: 1}}>Export</Typography>
|
||||
</Button>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
) : null }
|
||||
|
||||
</Grid>
|
||||
{/* Export Report */}
|
||||
{exportReport && exportReport.useExport ? (
|
||||
<Grid item xs={12} lg={2} xl={2}>
|
||||
<FormControl fullWidth>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ p: 2 }}
|
||||
onClick={() => exportReport.handleExportReport}
|
||||
>
|
||||
<Download />
|
||||
<Typography variant="inherit" sx={{ marginLeft: 1 }}>
|
||||
Export
|
||||
</Typography>
|
||||
</Button>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
) : null}
|
||||
</Grid>
|
||||
{/* End Field 1 */}
|
||||
{/* Field 2 */}
|
||||
<Grid item xs={12}>
|
||||
{/* Table */}
|
||||
<TableContainer component={Paper}>
|
||||
<TableContent aria-label="collapsible table" size="small">
|
||||
{/* Table Header */}
|
||||
<EnhancedTableHead />
|
||||
{/* End Table Header */}
|
||||
{/* Table Body */}
|
||||
<TableBody>
|
||||
{loadings.isLoading && rows.length >= 1 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={headCells?.length} align="center">
|
||||
Loading . . .
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : rows && rows.length >= 1 ? (
|
||||
rows.map((row, rowIndex) => (
|
||||
<TableRow key={rowIndex}>
|
||||
{headCells &&
|
||||
//@ts-ignore
|
||||
headCells.map((head, headIndex) => (
|
||||
//@ts-ignore
|
||||
<TableCell align={head.align} key={headIndex}>
|
||||
{row[head.id]}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} align="center">
|
||||
No Data Found
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
{/* End Table Body */}
|
||||
</TableContent>
|
||||
</TableContainer>
|
||||
{/* End Table */}
|
||||
|
||||
{/* Pagination */}
|
||||
<BaseTablePagination
|
||||
count={paginations.paginationTable.total}
|
||||
onPageChange={onPageChangeHandle}
|
||||
page={paginations.page}
|
||||
rowsPerPage={paginations.rowsPerPage}
|
||||
onRowsPerPageChange={onRowsPerPageChangeHandle}
|
||||
/>
|
||||
{/* End Pagination */}
|
||||
</Grid>
|
||||
{/* End Field 2 */}
|
||||
</Grid>
|
||||
{/* End Field 1 */}
|
||||
{/* Field 2 */}
|
||||
<Grid item xs={12}>
|
||||
{/* Table */}
|
||||
<TableContainer component={Paper}>
|
||||
<TableContent aria-label="collapsible table" size="small">
|
||||
{/* Table Header */}
|
||||
<EnhancedTableHead />
|
||||
{/* End Table Header */}
|
||||
{/* Table Body */}
|
||||
<TableBody>
|
||||
{loadings.isLoading && rows && rows.length >= 1 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={headCells?.length} align="center">
|
||||
Loading . . .
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : rows && rows.length >= 1 ? (
|
||||
rows.map((row, rowIndex) => (
|
||||
<TableRow key={rowIndex}>
|
||||
{headCells &&
|
||||
//@ts-ignore
|
||||
headCells.map((head, headIndex) => (
|
||||
//@ts-ignore
|
||||
<TableCell align={head.align} key={headIndex}>
|
||||
{row[head.id]}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} align="center">
|
||||
No Data Found
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
{/* End Table Body */}
|
||||
</TableContent>
|
||||
</TableContainer>
|
||||
{/* End Table */}
|
||||
|
||||
{/* Pagination */}
|
||||
<BaseTablePagination
|
||||
count={paginations.paginationTable.total}
|
||||
onPageChange={onPageChangeHandle}
|
||||
page={paginations.page}
|
||||
rowsPerPage={paginations.rowsPerPage}
|
||||
onRowsPerPageChange={onRowsPerPageChangeHandle}
|
||||
/>
|
||||
{/* End Pagination */}
|
||||
</Grid>
|
||||
{/* End Field 2 */}
|
||||
</Grid>
|
||||
// </Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,9 +60,8 @@ export default function DashboardLayout() {
|
||||
|
||||
const [corporateValue, setCorporateValue] = useLocalStorage(
|
||||
'corporateValue',
|
||||
user.corporate ? `${user.corporate.id}` : ''
|
||||
user && user.corporate ? `${user.corporate.id}` : ''
|
||||
);
|
||||
const value = { corporateValue, setCorporateValue };
|
||||
|
||||
if (verticalLayout) {
|
||||
return (
|
||||
@@ -96,7 +95,7 @@ export default function DashboardLayout() {
|
||||
}
|
||||
|
||||
return (
|
||||
<UserCurrentCorporateContext.Provider value={value}>
|
||||
<UserCurrentCorporateContext.Provider value={{ corporateValue, setCorporateValue }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: { lg: 'flex' },
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Stack } from '@mui/system';
|
||||
import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate';
|
||||
import Table from '../../components/Table';
|
||||
import { HeadCell, Order, PaginationTableProps } from '../../@types/table';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useLocation, useSearchParams } from 'react-router-dom';
|
||||
import palette from '../../theme/palette';
|
||||
import { fSplit } from '../../utils/formatNumber';
|
||||
|
||||
@@ -80,6 +80,7 @@ type CardPolicyProps = {
|
||||
export default function Index() {
|
||||
const { themeStretch } = useSettings();
|
||||
const { corporateValue } = useContext(UserCurrentCorporateContext);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [memberData, setMemberData] = useState([]);
|
||||
const [policyData, setPolicyData] = useState<CardPolicyProps>();
|
||||
@@ -257,8 +258,6 @@ export default function Index() {
|
||||
(async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
|
||||
const parameters =
|
||||
Object.keys(appliedParams).length !== 0
|
||||
? appliedParams
|
||||
@@ -270,10 +269,12 @@ export default function Index() {
|
||||
params: { ...parameters },
|
||||
});
|
||||
|
||||
// console.log('member', corporateMembers);
|
||||
const corporateTopUpLimit = await axios.get(`${corporateValue}/topup`);
|
||||
|
||||
setSearchParams(parameters);
|
||||
if (pathname === '/dashboard') {
|
||||
setSearchParams(parameters);
|
||||
}
|
||||
|
||||
setPolicyData({
|
||||
limit: corporatePolicyLimit.data.data,
|
||||
topUpLimit: corporateTopUpLimit.data.data,
|
||||
@@ -325,10 +326,10 @@ export default function Index() {
|
||||
</Button>
|
||||
),
|
||||
/* action: (
|
||||
<IconButton onClick={handleAction}>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
), */
|
||||
<IconButton onClick={handleAction}>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
), */
|
||||
}))
|
||||
);
|
||||
setPaginationTable(corporateMembers.data);
|
||||
|
||||
@@ -85,6 +85,31 @@ export default function List() {
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* ------------------------------ handle search ----------------------------- */
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const handleSearchSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (searchText === '') {
|
||||
searchParams.delete('search');
|
||||
const params = Object.fromEntries([...searchParams.entries()]);
|
||||
setAppliedParams(params);
|
||||
} else {
|
||||
const params = Object.fromEntries([...searchParams.entries(), ['search', searchText]]);
|
||||
setAppliedParams(params);
|
||||
}
|
||||
};
|
||||
|
||||
const searchs = {
|
||||
useSearchs: true,
|
||||
fullWidth: true,
|
||||
searchText: searchText,
|
||||
setSearchText: setSearchText,
|
||||
handleSearchSubmit: handleSearchSubmit,
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------- headCell -------------------------------- */
|
||||
const headCells: HeadCell<never>[] = [
|
||||
{
|
||||
@@ -146,11 +171,6 @@ export default function List() {
|
||||
setData(
|
||||
response.data.data.map((obj: any) => ({
|
||||
...obj,
|
||||
// memberId:
|
||||
// <Button
|
||||
// onClick={() => navigate ('/employee-data/user-profile/'+obj.personId)}
|
||||
// >{obj.memberId}</Button>
|
||||
// ,
|
||||
status:
|
||||
obj.status === 1 ? (
|
||||
<Label color="success">Active</Label>
|
||||
@@ -200,7 +220,7 @@ export default function List() {
|
||||
paginations={paginations}
|
||||
loadings={loadings}
|
||||
params={params}
|
||||
// searchs={searchs}
|
||||
searchs={searchs}
|
||||
// filters={filters}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user