Merge branch 'feature/client-portal-dashboard' into staging
This commit is contained in:
@@ -21,12 +21,14 @@ class CorporateMemberService
|
||||
$corporateEmployee->where('corporate_id', $corporateId);
|
||||
})
|
||||
->when($request->input('search'), function (Builder $query, $search) {
|
||||
$query->where('member_id', 'like', "%" . $search . "%")
|
||||
->orWhere('name', 'like', "%" . $search . "%");
|
||||
$query->where(function (Builder $query) use ($search) {
|
||||
$query->orWhere('members.member_id', 'like', "%" . $search . "%")
|
||||
->orWhere('members.name', 'like', "%" . $search . "%");
|
||||
});
|
||||
})
|
||||
->when($request->input('division'), function (Builder $division, $division_id) {
|
||||
$division->whereHas('division', function ($corporateEmployee) use ($division_id) {
|
||||
$corporateEmployee->where('division_id', $division_id);
|
||||
->when($request->input('division'), function (Builder $division, $value) {
|
||||
$division->whereHas('division', function (Builder $corporateEmployee) use ($value) {
|
||||
$corporateEmployee->where('division_id', $value);
|
||||
});
|
||||
})
|
||||
->when($request->has('orderBy'), function (Builder $query) use ($request) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { SelectChangeEvent } from '@mui/material';
|
||||
import { Dispatch, FormEvent, SetStateAction } from 'react';
|
||||
|
||||
/* ------------------------------- pagination ------------------------------- */
|
||||
export type PaginationTableProps = {
|
||||
@@ -34,6 +35,13 @@ export type HeadCell<DataType> = {
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* ----------------------------- division filter ---------------------------- */
|
||||
export type DivisionData = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* ----------------------------------- row ---------------------------------- */
|
||||
export type TableListProps<DataType> = {
|
||||
headCells?: HeadCell<DataType>[];
|
||||
@@ -62,5 +70,19 @@ export type TableListProps<DataType> = {
|
||||
appliedParams: {};
|
||||
setAppliedParams: Dispatch<SetStateAction<{}>>;
|
||||
};
|
||||
searchs: {
|
||||
searchText: string;
|
||||
setSearchText: Dispatch<SetStateAction<string>>;
|
||||
handleSearchSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
||||
};
|
||||
filters?: {
|
||||
useFilter: boolean;
|
||||
config: {
|
||||
label: string;
|
||||
divisionValue: string;
|
||||
divisionData: DivisionData[];
|
||||
handleDivisionChange: (event: SelectChangeEvent) => void;
|
||||
};
|
||||
};
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
@@ -28,7 +28,7 @@ import { visuallyHidden } from '@mui/utils';
|
||||
/* ---------------------------------- axios --------------------------------- */
|
||||
import axios from '../utils/axios';
|
||||
/* ---------------------------------- react --------------------------------- */
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { Fragment, useContext, useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
/* -------------------------------- component ------------------------------- */
|
||||
import BaseTablePagination from './BaseTablePagination';
|
||||
@@ -61,6 +61,8 @@ export default function Table<T>({
|
||||
orders,
|
||||
loadings,
|
||||
params,
|
||||
filters,
|
||||
searchs,
|
||||
}: TableListProps<T>) {
|
||||
/* ------------------------------- handle sort ------------------------------ */
|
||||
const handleRequestSort = async (event: React.MouseEvent<unknown>, property: string) => {
|
||||
@@ -120,46 +122,6 @@ export default function Table<T>({
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* ----------------------------- division field ----------------------------- */
|
||||
// const [divisionValue, setDivisionValue] = useState('all');
|
||||
// const [divisionData, setDivisionData] = useState([]);
|
||||
|
||||
// const handleDivisionChange = (event: SelectChangeEvent) => {
|
||||
// setDivisionValue(event.target.value as string);
|
||||
|
||||
// if (event.target.value === 'all') {
|
||||
// searchParams.delete('division');
|
||||
// const params = Object.fromEntries([...searchParams.entries()]);
|
||||
// setAppliedParams(params);
|
||||
// } else {
|
||||
// const params = Object.fromEntries([
|
||||
// ...searchParams.entries(),
|
||||
// ['division', event.target.value as string],
|
||||
// ]);
|
||||
// setAppliedParams(params);
|
||||
// }
|
||||
// };
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* ------------------------------ Search field ------------------------------ */
|
||||
// const [searchText, setSearchText] = useState('');
|
||||
|
||||
// const handleSearchSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
// event.preventDefault();
|
||||
// setIsLoading(true);
|
||||
// if (searchText === '') {
|
||||
// searchParams.delete('search');
|
||||
// const params = Object.fromEntries([...searchParams.entries()]);
|
||||
// setAppliedParams(params);
|
||||
// } else {
|
||||
// const params = Object.fromEntries([...searchParams.entries(), ['search', searchText]]);
|
||||
// setAppliedParams(params);
|
||||
// }
|
||||
// await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
// setIsLoading(false);
|
||||
// };
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* ------------------------ button change pagination ------------------------ */
|
||||
const onPageChangeHandle = async (
|
||||
event: React.MouseEvent<HTMLButtonElement> | null,
|
||||
@@ -194,20 +156,22 @@ export default function Table<T>({
|
||||
<Card>
|
||||
<Grid container>
|
||||
{/* Field 1 */}
|
||||
{/* <Grid item xs={12} paddingX="24px" paddingY="20px">
|
||||
<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={divisionValue}
|
||||
value={filters.config.divisionValue}
|
||||
label="Division"
|
||||
onChange={handleDivisionChange}
|
||||
onChange={filters.config.handleDivisionChange}
|
||||
>
|
||||
<MenuItem value="all">All</MenuItem>
|
||||
{divisionData.map((row: DivisionDataProps, index) => (
|
||||
{filters.config.divisionData.map((row: DivisionDataProps, index) => (
|
||||
<MenuItem key={index} value={row.id}>
|
||||
{row.name}
|
||||
</MenuItem>
|
||||
@@ -216,19 +180,34 @@ export default function Table<T>({
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={9} xl={10}>
|
||||
<form onSubmit={handleSearchSubmit}>
|
||||
<form onSubmit={searchs.handleSearchSubmit}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
onChange={(event) => setSearchText(event.target.value)}
|
||||
value={searchText}
|
||||
onChange={(event) => searchs.setSearchText(event.target.value)}
|
||||
value={searchs.searchText}
|
||||
fullWidth
|
||||
/>
|
||||
</form>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Grid item xs={12}>
|
||||
<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>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid> */}
|
||||
{/* End Field 1 */}
|
||||
{/* Field 2 */}
|
||||
<Grid item xs={12}>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
linearProgressClasses,
|
||||
SelectChangeEvent,
|
||||
} from '@mui/material';
|
||||
// hooks
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
@@ -22,11 +23,10 @@ import { Stack } from '@mui/system';
|
||||
import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate';
|
||||
import { PolicyProps } from '../../@types/policy';
|
||||
import Table from '../../components/Table';
|
||||
import { HeadCell, Order, PaginationTableProps } from '../../@types/table';
|
||||
import { DivisionData, HeadCell, Order, PaginationTableProps } from '../../@types/table';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import palette from '../../theme/palette';
|
||||
import { MoreVert as MoreVertIcon } from '@mui/icons-material';
|
||||
import TableList from '../../sections/dashboard/TableList';
|
||||
import { fSplit } from '../../utils/formatNumber';
|
||||
|
||||
const itemList = [
|
||||
@@ -130,6 +130,60 @@ export default function Index() {
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* ------------------------------ 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 = {
|
||||
searchText: searchText,
|
||||
setSearchText: setSearchText,
|
||||
handleSearchSubmit: handleSearchSubmit,
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* ------------------------------ handle filter ----------------------------- */
|
||||
const [divisionValue, setDivisionValue] = useState('all');
|
||||
const [divisionData, setDivisionData] = useState([]);
|
||||
|
||||
const handleDivisionChange = (event: SelectChangeEvent) => {
|
||||
setDivisionValue(event.target.value as string);
|
||||
|
||||
if (event.target.value === 'all') {
|
||||
searchParams.delete('division');
|
||||
const params = Object.fromEntries([...searchParams.entries()]);
|
||||
setAppliedParams(params);
|
||||
} else {
|
||||
const params = Object.fromEntries([
|
||||
...searchParams.entries(),
|
||||
['division', event.target.value as string],
|
||||
]);
|
||||
setAppliedParams(params);
|
||||
}
|
||||
};
|
||||
|
||||
const filters = {
|
||||
useFilter: true,
|
||||
config: {
|
||||
label: 'Division',
|
||||
divisionValue: divisionValue,
|
||||
divisionData: divisionData,
|
||||
handleDivisionChange: handleDivisionChange,
|
||||
},
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------- headCell -------------------------------- */
|
||||
const headCells: HeadCell<never>[] = [
|
||||
{
|
||||
@@ -181,6 +235,7 @@ export default function Index() {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setIsLoading(true);
|
||||
let mounted = true;
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
|
||||
@@ -196,17 +251,30 @@ export default function Index() {
|
||||
params: { ...parameters },
|
||||
});
|
||||
|
||||
setPolicyData(corporatePolicyLimit.data.data);
|
||||
|
||||
setSearchParams(parameters);
|
||||
|
||||
if (mounted) {
|
||||
setPolicyData(corporatePolicyLimit.data.data);
|
||||
setDivisionData(corporateDivision.data);
|
||||
setMemberData(corporateMembers.data.data);
|
||||
setPaginationTable(corporateMembers.data);
|
||||
setRowsPerPage(corporateMembers.data.per_page);
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
})();
|
||||
}, [appliedParams, searchParams, order, orderBy, setSearchParams, corporateValue]);
|
||||
|
||||
/* ------------------------------- card policy ------------------------------ */
|
||||
const newPolicyData = {
|
||||
limit: policyData,
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
const newMemberData: any = memberData.map((obj: any) => {
|
||||
return {
|
||||
...obj,
|
||||
@@ -270,7 +338,7 @@ export default function Index() {
|
||||
<CardNotification data={itemList} />
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={6} md={12}>
|
||||
<CardPolicy data={policyData} />
|
||||
<CardPolicy data={newPolicyData} />
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={12} md={12}>
|
||||
<Table
|
||||
@@ -280,8 +348,9 @@ export default function Index() {
|
||||
paginations={paginations}
|
||||
loadings={loadings}
|
||||
params={params}
|
||||
searchs={searchs}
|
||||
filters={filters}
|
||||
/>
|
||||
{/* <TableList /> */}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
|
||||
@@ -22,6 +22,7 @@ import DialogClaimSubmitMember from './DialogClaimSubmitMember';
|
||||
|
||||
type CardPolicyProps = {
|
||||
data: {
|
||||
limit: {
|
||||
myLimit: {
|
||||
balance: number;
|
||||
total: number;
|
||||
@@ -32,6 +33,16 @@ type CardPolicyProps = {
|
||||
percentage: number;
|
||||
};
|
||||
};
|
||||
// topUpLimit: {
|
||||
// companyName: string;
|
||||
// policyNumber: number;
|
||||
// totalMembers: number;
|
||||
// totalCases: number;
|
||||
// totalPersen: number;
|
||||
// myLimit: number;
|
||||
// totalLimit: number;
|
||||
// };
|
||||
};
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@@ -65,7 +76,7 @@ export default function CardPolicy(props: CardPolicyProps) {
|
||||
const [dialogTitle, setDialogTitle] = useState('');
|
||||
const [isDialog, setIsDialog] = useState('');
|
||||
|
||||
const { myLimit, lockLimit } = props.data;
|
||||
const { limit } = props.data;
|
||||
|
||||
const clickHandler = (isDialog: string) => {
|
||||
switch (isDialog) {
|
||||
@@ -94,23 +105,23 @@ export default function CardPolicy(props: CardPolicyProps) {
|
||||
Total Limit
|
||||
</Typography>
|
||||
<Typography sx={{ typography: 'body2' }}>
|
||||
{fCurrency(myLimit ? myLimit.balance : 0)}
|
||||
{fCurrency(limit.myLimit ? limit.myLimit.balance : 0)}
|
||||
</Typography>
|
||||
<Typography sx={{ typography: 'caption', color: '#919EAB' }}>
|
||||
/ {fSplit(myLimit ? myLimit.total : 0)}
|
||||
/ {fSplit(limit.myLimit ? limit.myLimit.total : 0)}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||
<Typography variant="h5" sx={{ ml: 0.5 }}>
|
||||
{myLimit ? myLimit.percentage : 0}%
|
||||
{limit.myLimit ? limit.myLimit.percentage : 0}%
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<BorderLinearProgress
|
||||
variant="determinate"
|
||||
value={myLimit ? myLimit.percentage : 0}
|
||||
value={limit.myLimit ? limit.myLimit.percentage : 0}
|
||||
sx={{ mb: 1 }}
|
||||
/>
|
||||
|
||||
@@ -123,11 +134,12 @@ export default function CardPolicy(props: CardPolicyProps) {
|
||||
sx={{ color: '#424242', marginRight: '6px' }}
|
||||
/>
|
||||
<Typography variant="caption" component="span">
|
||||
Lock Fund ( {lockLimit ? lockLimit.percentage : 0}% )
|
||||
Lock Fund ( {limit.lockLimit ? limit.lockLimit.percentage : 0}% )
|
||||
</Typography>
|
||||
</Typography>
|
||||
<Typography sx={{ typography: 'caption', color: '#637381' }}>
|
||||
{fSplit(lockLimit ? lockLimit.balance : 0)} / {fSplit(myLimit ? myLimit.total : 0)}
|
||||
{fSplit(limit.lockLimit ? limit.lockLimit.balance : 0)} /{' '}
|
||||
{fSplit(limit.myLimit ? limit.myLimit.total : 0)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -21,14 +21,7 @@ import * as Yup from 'yup';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type DataContent = {
|
||||
info: string;
|
||||
date: string;
|
||||
time: string;
|
||||
};
|
||||
|
||||
/* ---------------------------------- types --------------------------------- */
|
||||
type MuiDialogProps = {
|
||||
title?: {
|
||||
name?: string;
|
||||
@@ -37,24 +30,31 @@ type MuiDialogProps = {
|
||||
openDialog: boolean;
|
||||
setOpenDialog: Function;
|
||||
content?: ReactElement;
|
||||
data?: DataContent[];
|
||||
data?: {
|
||||
companyName: string;
|
||||
policyNumber: number;
|
||||
totalMembers: number;
|
||||
totalCases: number;
|
||||
totalPersen: number;
|
||||
myLimit: number;
|
||||
totalLimit: number;
|
||||
};
|
||||
};
|
||||
|
||||
type FormValuesProps = {
|
||||
topup: string;
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const testData = {
|
||||
companyName: 'PT. Aman Mineral',
|
||||
policyNumber: 12345678,
|
||||
totalMembers: 50,
|
||||
totalCases: 100,
|
||||
totalPersen: 75,
|
||||
myLimit: 375000000,
|
||||
totalLimit: 500000000,
|
||||
};
|
||||
// const testData = {
|
||||
// companyName: 'PT. Aman Mineral',
|
||||
// policyNumber: 12345678,
|
||||
// totalMembers: 50,
|
||||
// totalCases: 100,
|
||||
// totalPersen: 75,
|
||||
// myLimit: 375000000,
|
||||
// totalLimit: 500000000,
|
||||
// };
|
||||
|
||||
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
|
||||
height: 10,
|
||||
@@ -70,7 +70,12 @@ const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const DialogTopUpLimit = ({ title, openDialog, setOpenDialog, data }: MuiDialogProps) => {
|
||||
export default function DialogTopUpLimit({
|
||||
title,
|
||||
openDialog,
|
||||
setOpenDialog,
|
||||
data,
|
||||
}: MuiDialogProps) {
|
||||
const [isDisabledCheckbox, setIsDisabledCheckbox] = useState(false);
|
||||
const [isDisabledButton, setIsDisabledButton] = useState(true);
|
||||
|
||||
@@ -126,42 +131,42 @@ const DialogTopUpLimit = ({ title, openDialog, setOpenDialog, data }: MuiDialogP
|
||||
<Typography variant="caption" color="#637381">
|
||||
Company Name
|
||||
</Typography>
|
||||
<Typography variant="body2">{testData.companyName}</Typography>
|
||||
<Typography variant="body2">{data ? data.companyName : ''}</Typography>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Typography variant="caption" color="#637381">
|
||||
Policy Number
|
||||
</Typography>
|
||||
<Typography variant="body2">{testData.policyNumber}</Typography>
|
||||
<Typography variant="body2">{data ? data.policyNumber : 0}</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={22}>
|
||||
<Stack>
|
||||
<Typography variant="caption" color="#637381">
|
||||
Total Member
|
||||
</Typography>
|
||||
<Typography variant="body2">{testData.totalMembers} Person</Typography>
|
||||
<Typography variant="body2">{data ? data.totalMembers : 0} Person</Typography>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Typography variant="caption" color="#637381">
|
||||
Total Cases
|
||||
</Typography>
|
||||
<Typography variant="body2">{testData.totalCases} Cases</Typography>
|
||||
<Typography variant="body2">{data ? data.totalCases : 0} Cases</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack spacing={1} sx={{ backgroundColor: '#F4F6F8', borderRadius: 1.5, padding: 2 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Stack>
|
||||
<Typography variant="body2">Company Pooled Fund</Typography>
|
||||
<Typography variant="body2">{fCurrency(testData.myLimit)}</Typography>
|
||||
<Typography variant="body2">{fCurrency(data ? data.myLimit : 0)}</Typography>
|
||||
<Typography variant="caption" color="#919EAB">
|
||||
/ {testData.totalLimit}
|
||||
/ {data ? data.totalLimit : 0}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Typography variant="h5">{testData.totalPersen}%</Typography>
|
||||
<Typography variant="h5">{data ? data.totalPersen : 0}%</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<BorderLinearProgress variant="determinate" value={testData.totalPersen} />
|
||||
<BorderLinearProgress variant="determinate" value={data ? data.totalPersen : 0} />
|
||||
</Stack>
|
||||
<Stack spacing={2}>
|
||||
<Typography variant="subtitle1" marginTop={3}>
|
||||
@@ -178,7 +183,7 @@ const DialogTopUpLimit = ({ title, openDialog, setOpenDialog, data }: MuiDialogP
|
||||
<FormControlLabel
|
||||
sx={{ typography: 'caption' }}
|
||||
control={<Checkbox />}
|
||||
label={'Max ' + fCurrency(testData.totalLimit - testData.myLimit)}
|
||||
label={'Max ' + fCurrency((data ? data.totalLimit : 0) - (data ? data.myLimit : 0))}
|
||||
onChange={handleSubmit(onCheckHandler)}
|
||||
/>
|
||||
|
||||
@@ -207,6 +212,4 @@ const DialogTopUpLimit = ({ title, openDialog, setOpenDialog, data }: MuiDialogP
|
||||
maxWidth="xs"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DialogTopUpLimit;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user