working on patrol details and do some update on timeline
This commit is contained in:
@@ -1,13 +1,69 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {Box, Button, Chip, Divider, Slide} from "@mui/material";
|
import {
|
||||||
import {useState} from "react";
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Chip,
|
||||||
|
Divider,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
Slide,
|
||||||
|
Stack,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import React, {useEffect, useState} from "react";
|
||||||
import CarCode from "@/core/components/CarCode";
|
import CarCode from "@/core/components/CarCode";
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import {AdapterDateFnsJalali} from "@mui/x-date-pickers/AdapterDateFnsJalali";
|
||||||
|
import {faIR} from "@mui/x-date-pickers/locales";
|
||||||
|
import {LocalizationProvider, MobileDateTimePicker} from "@mui/x-date-pickers";
|
||||||
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import MapLoading from "@/core/components/MapLayer/Loading";
|
||||||
|
import LocalGasStationIcon from '@mui/icons-material/LocalGasStation';
|
||||||
|
import DirectionsCarFilledIcon from '@mui/icons-material/DirectionsCarFilled';
|
||||||
|
import WatchLaterIcon from '@mui/icons-material/WatchLater';
|
||||||
|
import AddRoadIcon from '@mui/icons-material/AddRoad';
|
||||||
|
import ShareLocationIcon from '@mui/icons-material/ShareLocation';
|
||||||
|
import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft";
|
||||||
|
import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
|
||||||
|
|
||||||
|
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||||
|
loading: () => <MapLoading/>,
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
const PatrolDetail = ({tabState, setTabState}) => {
|
const PatrolDetail = ({tabState, setTabState}) => {
|
||||||
const [patrolFounded, setPatrolFounded] = useState(false);
|
const [patrolFounded, setPatrolFounded] = useState(false);
|
||||||
const [carCode, setCarCode] = useState(null);
|
const [carCode, setCarCode] = useState(null);
|
||||||
|
const [dateFrom, setDateFrom] = useState(null);
|
||||||
|
const [dateTo, setDateTo] = useState(null);
|
||||||
|
const [readyToRequest, setReadyToRequest] = useState(false);
|
||||||
|
const [data, setData] = useState({
|
||||||
|
machine_code: "12-213-21",
|
||||||
|
distance: "23",
|
||||||
|
time: "04:45",
|
||||||
|
stop: "2",
|
||||||
|
fuel_amount: "21"
|
||||||
|
})
|
||||||
|
|
||||||
|
const requestPatrolInfo = () => {
|
||||||
|
setPatrolFounded(true)
|
||||||
|
setReadyToRequest(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("data", carCode?.id, dateFrom, dateTo)
|
||||||
|
if (carCode !== null && dateFrom !== null && dateTo !== null) {
|
||||||
|
setReadyToRequest(true);
|
||||||
|
} else {
|
||||||
|
setReadyToRequest(false);
|
||||||
|
}
|
||||||
|
}, [carCode, dateFrom, dateTo]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -19,18 +75,187 @@ const PatrolDetail = ({tabState, setTabState}) => {
|
|||||||
my: 3
|
my: 3
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{display: "flex", gap: 2}}>
|
<Box sx={{display: "flex", flexDirection: {xs: "column", lg: "row"}, gap: 2}}>
|
||||||
<CarCode carCode={carCode} setCarCode={setCarCode}/>
|
<Box sx={{minWidth: "200px"}}>
|
||||||
|
<CarCode carCode={carCode} setCarCode={setCarCode}/>
|
||||||
<Button variant="contained" color="success" endIcon={<SearchIcon/>}>درخواست اطلاعات</Button>
|
</Box>
|
||||||
|
<Box sx={{display: "flex", gap: 2}}>
|
||||||
|
<LocalizationProvider
|
||||||
|
dateAdapter={AdapterDateFnsJalali}
|
||||||
|
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||||
|
>
|
||||||
|
<MobileDateTimePicker value={dateFrom ? new Date(dateFrom) : null}
|
||||||
|
ampm={false}
|
||||||
|
name="تاریخ و ساعت شروع گشت"
|
||||||
|
closeOnSelect
|
||||||
|
maxDateTime={dateTo ? new Date(dateTo) : null}
|
||||||
|
onChange={(dateFrom) => {
|
||||||
|
const date = new Date(dateFrom);
|
||||||
|
const formattedDate = moment(date).locale("en").format("YYYY-MM-DD HH:mm");
|
||||||
|
setDateFrom(formattedDate);
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
textField: {
|
||||||
|
size: "small",
|
||||||
|
placeholder: "تاریخ و ساعت شروع گشت",
|
||||||
|
InputProps: {
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setDateFrom(null);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: "#bfbfbf",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||||
|
color: "#363434",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ClearIcon/>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}} label="تاریخ و ساعت شروع گشت"
|
||||||
|
/>
|
||||||
|
<MobileDateTimePicker value={dateTo ? new Date(dateTo) : null}
|
||||||
|
ampm={false}
|
||||||
|
name="تاریخ و ساعت پایان گشت"
|
||||||
|
closeOnSelect
|
||||||
|
minDateTime={dateFrom ? new Date(dateFrom) : null}
|
||||||
|
onChange={(dateTo) => {
|
||||||
|
const date = new Date(dateTo);
|
||||||
|
const formattedDate = moment(date).locale("en").format("YYYY-MM-DD HH:mm");
|
||||||
|
setDateTo(formattedDate);
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
textField: {
|
||||||
|
size: "small",
|
||||||
|
placeholder: "تاریخ و ساعت پایان گشت",
|
||||||
|
InputProps: {
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setDateTo(null);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: "#bfbfbf",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||||
|
color: "#363434",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ClearIcon/>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}} label="تاریخ و ساعت پایان گشت"/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</Box>
|
||||||
|
<Button variant="contained" onClick={requestPatrolInfo} color="success" disabled={!readyToRequest}
|
||||||
|
endIcon={<SearchIcon/>}>درخواست
|
||||||
|
اطلاعات</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Divider sx={{my: 2, width: "100%"}}>
|
<Divider sx={{my: 2, width: "100%"}}>
|
||||||
<Chip color="primary" label="مشخصات گشت"/>
|
<Chip color="success" label="مشخصات گشت"/>
|
||||||
</Divider>
|
</Divider>
|
||||||
<Slide in={patrolFounded} sx={{my: 5, width: {xs: "100%", sm: "70%"}}}>
|
{patrolFounded && <Slide in={patrolFounded} sx={{width: "100%"}}>
|
||||||
<Box>
|
<Card>
|
||||||
</Box>
|
<CardContent
|
||||||
</Slide>
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: {xs: "column", lg: "row"},
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
px: 2,
|
||||||
|
gap: {xs: 2, lg: 0},
|
||||||
|
py: 2
|
||||||
|
}}>
|
||||||
|
<Stack sx={{minWidth: {xs: "100%", lg: "300px"}, gap: 2}}>
|
||||||
|
<Box sx={{display: "flex", alignItems: "center", justifyContent: "space-between"}}>
|
||||||
|
<Chip label="کد ماشین" icon={<DirectionsCarFilledIcon/>}/>
|
||||||
|
<Divider sx={{flex: 1, mx: 2}}/>
|
||||||
|
<Typography sx={{fontSize: "14px", fontWeight: 500, textDecoration: "underline"}}>
|
||||||
|
{data.machine_code}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{display: "flex", alignItems: "center", justifyContent: "space-between"}}>
|
||||||
|
<Chip label="بازه زمانی" icon={<WatchLaterIcon/>}/>
|
||||||
|
<Divider sx={{flex: 1, mx: 2}}/>
|
||||||
|
<Typography sx={{fontSize: "14px", fontWeight: 500, textDecoration: "underline"}}>
|
||||||
|
{data.time}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{display: "flex", alignItems: "center", justifyContent: "space-between"}}>
|
||||||
|
<Chip label="مسافت" icon={<AddRoadIcon/>}/>
|
||||||
|
<Divider sx={{flex: 1, mx: 2}}/>
|
||||||
|
<Typography sx={{fontSize: "14px", fontWeight: 500, textDecoration: "underline"}}>
|
||||||
|
{data.distance}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{display: "flex", alignItems: "center", justifyContent: "space-between"}}>
|
||||||
|
<Chip label="توقف در مسیر" icon={<ShareLocationIcon/>}/>
|
||||||
|
<Divider sx={{flex: 1, mx: 2}}/>
|
||||||
|
<Typography sx={{fontSize: "14px", fontWeight: 500, textDecoration: "underline"}}>
|
||||||
|
{data.stop}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{display: "flex", alignItems: "center", justifyContent: "space-between"}}>
|
||||||
|
<Chip label="میزان مصرف سوخت" icon={<LocalGasStationIcon/>}/>
|
||||||
|
<Divider sx={{flex: 1, mx: 2}}/>
|
||||||
|
<Typography sx={{fontSize: "14px", fontWeight: 500, textDecoration: "underline"}}>
|
||||||
|
{data.fuel_amount}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
<Divider orientation="vertical" flexItem/>
|
||||||
|
<Stack spacing={1} sx={{height: "300px", width: {xs: "100%", lg: "350px"}}}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: 1,
|
||||||
|
border: "1px dashed",
|
||||||
|
borderColor: "divider",
|
||||||
|
borderRadius: 1,
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MapLayer>
|
||||||
|
</MapLayer>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Slide>}
|
||||||
|
<Box sx={{display: "flex", gap: 1}}>
|
||||||
|
<Button variant="outlined" color="error"
|
||||||
|
startIcon={<KeyboardDoubleArrowRightIcon/>} sx={{mt: 2}}>
|
||||||
|
بازگشت
|
||||||
|
</Button>
|
||||||
|
<Button variant="contained" color="primary"
|
||||||
|
endIcon={<KeyboardDoubleArrowLeftIcon/>} sx={{mt: 2}}>
|
||||||
|
تایید اطلاعات و رفتن به مرحله بعد
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function TabPanel(props) {
|
|||||||
|
|
||||||
const PatrolForms = () => {
|
const PatrolForms = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
|
||||||
const [tabState, setTabState] = useState(0);
|
const [tabState, setTabState] = useState(0);
|
||||||
|
|
||||||
const handleChangeTab = (event, newValue) => {
|
const handleChangeTab = (event, newValue) => {
|
||||||
@@ -71,7 +71,7 @@ const PatrolForms = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<Box sx={{display: "flex", alignItems: "center"}}>
|
<Box sx={{display: "flex", alignItems: "center"}}>
|
||||||
<PatrolTimeLine/>
|
<PatrolTimeLine tabState={tabState}/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {Box, Typography} from "@mui/material";
|
import {Box, CircularProgress, Typography} from "@mui/material";
|
||||||
import TapAndPlayIcon from '@mui/icons-material/TapAndPlay';
|
import TapAndPlayIcon from '@mui/icons-material/TapAndPlay';
|
||||||
import TaxiAlertIcon from '@mui/icons-material/TaxiAlert';
|
import TaxiAlertIcon from '@mui/icons-material/TaxiAlert';
|
||||||
import EngineeringIcon from '@mui/icons-material/Engineering';
|
import EngineeringIcon from '@mui/icons-material/Engineering';
|
||||||
@@ -17,6 +17,8 @@ import {
|
|||||||
} from "@mui/lab";
|
} from "@mui/lab";
|
||||||
|
|
||||||
const PatrolTimeLine = ({tabState}) => {
|
const PatrolTimeLine = ({tabState}) => {
|
||||||
|
|
||||||
|
console.log("tabState", tabState)
|
||||||
return (
|
return (
|
||||||
<Box sx={{display: "flex", alignItems: "center"}}>
|
<Box sx={{display: "flex", alignItems: "center"}}>
|
||||||
<Timeline
|
<Timeline
|
||||||
@@ -29,10 +31,10 @@ const PatrolTimeLine = ({tabState}) => {
|
|||||||
<TimelineSeparator>
|
<TimelineSeparator>
|
||||||
<TimelineConnector/>
|
<TimelineConnector/>
|
||||||
<TimelineDot sx={{backgroundColor: "#fff"}}>
|
<TimelineDot sx={{backgroundColor: "#fff"}}>
|
||||||
<TapAndPlayIcon
|
{tabState === 0 ? <CircularProgress size="20px"/> : <TapAndPlayIcon
|
||||||
color={tabState === 0 ? "primary" : "error"}
|
color={tabState > 0 ? "success" : "error"}
|
||||||
sx={{width: "1.5rem", height: "1.5rem"}}
|
sx={{width: "1.5rem", height: "1.5rem"}}
|
||||||
/>
|
/>}
|
||||||
</TimelineDot>
|
</TimelineDot>
|
||||||
<TimelineConnector/>
|
<TimelineConnector/>
|
||||||
</TimelineSeparator>
|
</TimelineSeparator>
|
||||||
@@ -47,10 +49,10 @@ const PatrolTimeLine = ({tabState}) => {
|
|||||||
<TimelineSeparator>
|
<TimelineSeparator>
|
||||||
<TimelineConnector/>
|
<TimelineConnector/>
|
||||||
<TimelineDot sx={{backgroundColor: "#fff"}}>
|
<TimelineDot sx={{backgroundColor: "#fff"}}>
|
||||||
<TaxiAlertIcon
|
{tabState === 1 ? <CircularProgress size="20px"/> : <TaxiAlertIcon
|
||||||
color={tabState === 1 ? "primary" : "error"}
|
color={tabState > 1 ? "success" : "error"}
|
||||||
sx={{width: "1.5rem", height: "1.5rem"}}
|
sx={{width: "1.5rem", height: "1.5rem"}}
|
||||||
/>
|
/>}
|
||||||
</TimelineDot>
|
</TimelineDot>
|
||||||
<TimelineConnector/>
|
<TimelineConnector/>
|
||||||
</TimelineSeparator>
|
</TimelineSeparator>
|
||||||
@@ -65,10 +67,10 @@ const PatrolTimeLine = ({tabState}) => {
|
|||||||
<TimelineSeparator>
|
<TimelineSeparator>
|
||||||
<TimelineConnector/>
|
<TimelineConnector/>
|
||||||
<TimelineDot sx={{backgroundColor: "#fff"}}>
|
<TimelineDot sx={{backgroundColor: "#fff"}}>
|
||||||
<EngineeringIcon
|
{tabState === 2 ? <CircularProgress size="20px"/> : <EngineeringIcon
|
||||||
color={tabState === 1 ? "primary" : "error"}
|
color={tabState > 2 ? "success" : "error"}
|
||||||
sx={{width: "1.5rem", height: "1.5rem"}}
|
sx={{width: "1.5rem", height: "1.5rem"}}
|
||||||
/>
|
/>}
|
||||||
</TimelineDot>
|
</TimelineDot>
|
||||||
<TimelineConnector/>
|
<TimelineConnector/>
|
||||||
</TimelineSeparator>
|
</TimelineSeparator>
|
||||||
@@ -83,10 +85,10 @@ const PatrolTimeLine = ({tabState}) => {
|
|||||||
<TimelineSeparator>
|
<TimelineSeparator>
|
||||||
<TimelineConnector/>
|
<TimelineConnector/>
|
||||||
<TimelineDot sx={{backgroundColor: "#fff"}}>
|
<TimelineDot sx={{backgroundColor: "#fff"}}>
|
||||||
<HandymanIcon
|
{tabState === 3 ? <CircularProgress size="20px"/> : <HandymanIcon
|
||||||
color={tabState === 1 ? "primary" : "error"}
|
color={tabState > 3 ? "success" : "error"}
|
||||||
sx={{width: "1.5rem", height: "1.5rem"}}
|
sx={{width: "1.5rem", height: "1.5rem"}}
|
||||||
/>
|
/>}
|
||||||
</TimelineDot>
|
</TimelineDot>
|
||||||
<TimelineConnector/>
|
<TimelineConnector/>
|
||||||
</TimelineSeparator>
|
</TimelineSeparator>
|
||||||
@@ -101,10 +103,10 @@ const PatrolTimeLine = ({tabState}) => {
|
|||||||
<TimelineSeparator>
|
<TimelineSeparator>
|
||||||
<TimelineConnector/>
|
<TimelineConnector/>
|
||||||
<TimelineDot sx={{backgroundColor: "#fff"}}>
|
<TimelineDot sx={{backgroundColor: "#fff"}}>
|
||||||
<VerifiedIcon
|
{tabState === 4 ? <CircularProgress size="20px"/> : <VerifiedIcon
|
||||||
color={tabState === 1 ? "primary" : "error"}
|
color={tabState > 4 ? "success" : "error"}
|
||||||
sx={{width: "1.5rem", height: "1.5rem"}}
|
sx={{width: "1.5rem", height: "1.5rem"}}
|
||||||
/>
|
/>}
|
||||||
</TimelineDot>
|
</TimelineDot>
|
||||||
<TimelineConnector/>
|
<TimelineConnector/>
|
||||||
</TimelineSeparator>
|
</TimelineSeparator>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ export const GET_ROAD_PATROL_OPERATOR_LIST = "/v3/api/fake-road-patrol/operator"
|
|||||||
export const EXPORT_ROAD_PATROL_OPERATOR_LIST = "https://rms.rmto.ir/v2/road_patrols/operator/cartable/report";
|
export const EXPORT_ROAD_PATROL_OPERATOR_LIST = "https://rms.rmto.ir/v2/road_patrols/operator/cartable/report";
|
||||||
export const GET_ROAD_PATROL_SUPERVISOR_LIST = "/v3/api/fake-road-patrol/supervisor";
|
export const GET_ROAD_PATROL_SUPERVISOR_LIST = "/v3/api/fake-road-patrol/supervisor";
|
||||||
export const EXPORT_ROAD_PATROL_SUPERVISOR_LIST = "https://rms.rmto.ir/v2/road_patrols/supervisor/cartable/report";
|
export const EXPORT_ROAD_PATROL_SUPERVISOR_LIST = "https://rms.rmto.ir/v2/road_patrols/supervisor/cartable/report";
|
||||||
|
export const GET_OTP_TOKEN = "fake/api";
|
||||||
|
export const VERIFY_OTP = "fake/api";
|
||||||
|
|
||||||
// road items
|
// road items
|
||||||
export const GET_ROAD_ITEMS_SUPERVISOR_LIST = api + "/api/v3/road_items/supervisor_index";
|
export const GET_ROAD_ITEMS_SUPERVISOR_LIST = api + "/api/v3/road_items/supervisor_index";
|
||||||
@@ -48,4 +50,4 @@ export const REJECT_BY_SUPERVISOR = api + "/api/v3/road_items/verify_by_supervis
|
|||||||
export const DELETE_BY_SUPERVISOR = api + "/api/v3/road_items/delete";
|
export const DELETE_BY_SUPERVISOR = api + "/api/v3/road_items/delete";
|
||||||
export const RESTORE_BY_SUPERVISOR = api + "/api/v3/road_items/restore";
|
export const RESTORE_BY_SUPERVISOR = api + "/api/v3/road_items/restore";
|
||||||
export const GET_CAR_LIST_SEARCH = api + "/api/v3/cmms_machines/search";
|
export const GET_CAR_LIST_SEARCH = api + "/api/v3/cmms_machines/search";
|
||||||
export const GET_RAHDARANS_LIST_SEARCH = api + "/api/v3/rahdaran/search";
|
export const GET_RAHDARANS_LIST_SEARCH = api + "/api/v3/rahdaran/search";
|
||||||
Reference in New Issue
Block a user