Feature/amiriis teleport setad

This commit is contained in:
AmirHossein Mahmoodi
2025-08-16 06:25:39 +00:00
parent f3d1d54818
commit 5a75f8b2eb
20 changed files with 217 additions and 126 deletions

View File

@@ -0,0 +1,10 @@
import TeleportPage from "@/components/Teleport";
export const metadata = {
title: "درحال انتقال به استان مورد نظر",
};
const Page = () => {
return <TeleportPage />;
};
export default Page;

View File

@@ -46,7 +46,7 @@ const LoginForm = () => {
data: formData,
});
setToken(response.data.data.token);
} catch (error) {}
} catch (error) { }
};
return (

View File

@@ -0,0 +1,51 @@
'use client'
import LoadingHardPage from "@/core/components/LoadingHardPage";
import { TELEPORT_USER_ROUTE } from "@/core/utils/routes";
import { useAuth } from "@/lib/contexts/auth";
import useRequest from "@/lib/hooks/useRequest";
import { Stack, Typography } from "@mui/material";
import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
const TelePortPage = () => {
const { setToken } = useAuth()
const searchParams = useSearchParams();
const token = searchParams.get("token");
const user_id = searchParams.get("user_id");
const [error, setError] = useState(false)
const requestServer = useRequest({ notificationShow: false });
useEffect(() => {
const login = async () => {
try {
const response = await requestServer(TELEPORT_USER_ROUTE, "post", {
data: {
token: decodeURIComponent(token),
user_id: decodeURIComponent(user_id)
}
});
setToken(response.data.data.token);
} catch (error) { setError(true) }
}
login()
}, [])
return (
<LoadingHardPage
authState={error}
label={
error ? (
<Stack justifyContent={"center"} alignItems={"center"} spacing={2}>
<Typography variant={"body1"}>مشکلی در انتقال پیش آمده است!</Typography>
</Stack>
) : (
<Typography variant={"body1"}>درحال انتقال به مرکز مورد نظر...</Typography>
)
}
loading={true}
/>
)
}
export default TelePortPage

View File

@@ -34,6 +34,18 @@ const DataTable = () => {
grow: false,
size: 100,
},
{
accessorKey: "name",
header: "نام انگلیسی",
id: "name",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "created_at",
header: "تاریخ ساخت",

View File

@@ -39,6 +39,7 @@ const DataTable = () => {
size: 100,
},
{
accessorKey: "telephone_id",
header: "شماره تماس داخلی",
id: "telephone_id",
enableColumnFilter: true,
@@ -48,7 +49,6 @@ const DataTable = () => {
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
Cell: ({ row }) => <TelephoneId user_id={row.original.id} />,
},
{
accessorKey: "is_online",

View File

@@ -5,7 +5,7 @@ import OnlineUsersReport from "./OnlineUsersReport";
const DashboardPage = () => {
return (
<Stack>
<OnlineUsersReport />
{/* <OnlineUsersReport /> */}
</Stack>
);
};

View File

@@ -1,12 +1,12 @@
"use client";
import Profile from "@/core/components/Profile";
import { filterMenuItems } from "@/core/utils/filterMenuItems";
import { pageMenu } from "@/core/utils/pageMenu";
import { usePermissions } from "@/lib/hooks/usePermissions";
import { List } from "@mui/material";
import { usePathname } from "next/navigation";
import { useEffect, useReducer, useState } from "react";
import { filterMenuItems } from "@/core/utils/filterMenuItems";
import { pageMenu } from "@/core/utils/pageMenu";
import SidebarListItems from "./SidebarListItems";
import Profile from "@/core/components/Profile";
function selectPage(item, route) {
if (item.type === "page") {
@@ -53,16 +53,16 @@ function reducer(state, action) {
return state.map((item) =>
item.hasSubitems
? {
...item,
Subitems: item.Subitems.map((subitem) =>
subitem.hasSubitems
? {
...subitem,
Subitems: toggleSubitems(subitem.Subitems, action.id),
}
: subitem
),
}
...item,
Subitems: item.Subitems.map((subitem) =>
subitem.hasSubitems
? {
...subitem,
Subitems: toggleSubitems(subitem.Subitems, action.id),
}
: subitem
),
}
: item
);

View File

@@ -8,8 +8,11 @@ import HeaderMenu from "./HeaderMenu";
import moment from "jalali-moment";
import { headerMenu } from "@/core/utils/headerMenu";
import SidebarMenu from "./Sidebar/SidebarMenu";
import Teleporting from "@/core/components/Teleporting";
import { usePermissions } from "@/lib/hooks/usePermissions";
import WithWidgetMiddleware from "@/core/middlewares/withWidget";
const drawerWidth = 300;
const drawerWidth = 270;
const Main = styled(Stack, { shouldForwardProp: (prop) => prop !== "open" })(({ theme, open }) => ({
flexGrow: 1,
@@ -50,11 +53,13 @@ const DrawerHeader = styled("div")(({ theme }) => ({
alignItems: "center",
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
minHeight: "40px !important",
minHeight: "43px !important",
justifyContent: "flex-end",
}));
const HeaderWithSidebar = ({ children }) => {
const { data: permissions } = usePermissions();
const isTeleportToProvince = permissions?.some((permission) => permission === "teleport_to_province");
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("md")); // Detect mobile/tablet screen
const [open, setOpen] = useState(!isMobile); // Initial state based on screen size
@@ -103,9 +108,16 @@ const HeaderWithSidebar = ({ children }) => {
},
}}
>
{headerMenu.map((menu) => (
<HeaderMenu key={menu.title} menu={menu} />
))}
<Stack sx={{ flex: 1 }} direction={'row'} justifyContent={'space-between'}>
<Box sx={{ flex: 1 }}>
{headerMenu.map((menu) => (
<HeaderMenu key={menu.title} menu={menu} />
))}
</Box>
<WithWidgetMiddleware enable={isTeleportToProvince}>
<Teleporting />
</WithWidgetMiddleware>
</Stack>
</Box>
</Toolbar>
</AppBar>

View File

@@ -0,0 +1,102 @@
import { GET_TOKEN_TELEPORT_USER_ROUTE } from "@/core/utils/routes";
import { useAuth } from "@/lib/contexts/auth";
import useProvinces from "@/lib/hooks/useProvince";
import useRequest from "@/lib/hooks/useRequest";
import { Apartment } from "@mui/icons-material";
import { Box, CircularProgress, FormControl, InputLabel, MenuItem, Select, Stack, Typography, Zoom } from "@mui/material";
import { useMemo, useState } from "react";
const Teleporting = () => {
const { user } = useAuth()
const { provinces, errorProvinces, loadingProvinces } = useProvinces()
const getColumnSelectOptions = useMemo(() => {
if (loadingProvinces) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorProvinces) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
...provinces.map((province) => ({
value: province.id,
label: province.name,
})),
];
}, [provinces, errorProvinces, loadingProvinces]);
const requestServer = useRequest();
const [loading, setLoading] = useState(false);
const [province, setProvince] = useState(user.server_province_id);
const handleChange = async ({ target: { value } }) => {
setLoading(true);
setProvince(value)
try {
const { data } = await requestServer(GET_TOKEN_TELEPORT_USER_ROUTE, "post", {
data: {
province_id: value
},
});
const url = `http://${data.data.ip}/teleport`
const params = `?token=${encodeURIComponent(data.data.token)}&user_id=${encodeURIComponent(data.data.user_id)}`;
window.location.href = url + params
} catch (error) {
setLoading(false);
setProvince(user.server_province_id)
}
};
if (loading) {
return (
<Zoom in>
<Stack
sx={{ width: 170, height: 43 }}
justifyContent="center"
alignItems="center"
direction="row"
spacing={1}
>
<CircularProgress size="14px" color="inherit" />
<Typography variant="caption">در حال انتقال به {provinces.find(p => p.id == province).name}</Typography>
</Stack>
</Zoom>
);
}
return (
<Stack sx={{ width: 170, height: 43 }}>
<FormControl
variant="filled"
size="small"
sx={{
minWidth: 170,
"& .MuiInputBase-root, & .MuiInputLabel-root, & .MuiSvgIcon-root": {
color: "white",
fontSize: "12px",
},
}}
>
<InputLabel id="teleport-province-label">مرکز</InputLabel>
<Select
labelId="teleport-province-label"
id="teleport-province"
value={loadingProvinces ? "loading" : errorProvinces ? "error" : province}
onChange={handleChange}
>
{getColumnSelectOptions.map(option => (
<MenuItem dense key={option.value} value={option.value}>
<Stack direction={'row'} alignItems={'center'} spacing={2}>
<Apartment />
<Box>
{option.label}
</Box>
</Stack>
</MenuItem>
))}
</Select>
</FormControl>
</Stack>
);
};
export default Teleporting;

View File

@@ -1,5 +1,8 @@
const api = process.env.NEXT_PUBLIC_API_URL;
export const TELEPORT_USER_ROUTE = api + "/verify_authentication";
export const GET_TOKEN_TELEPORT_USER_ROUTE = api + "/login_to_province";
export const GET_USER_ROUTE = api + "/profile/info";
export const CHANGE_USER_PASSWORD = api + "/profile/change_password";
export const GET_USER_LOGIN_ROUTE = api + "/auth/login";

View File

@@ -36,6 +36,7 @@ const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [errorState, setErrorState] = useState({ status: null, message: "" });
const [state, dispatch] = useReducer(authReducer, initAuth);
const [tokenChecked, setTokenChecked] = useState(false);
const clearUser = useCallback(() => {
dispatch({ type: "CLEAR_USER" });
@@ -95,9 +96,12 @@ export const AuthProvider = ({ children }) => {
useEffect(() => {
const localToken = localStorage.getItem("_token");
if (localToken) dispatch({ type: "SET_TOKEN", token: localToken });
setTokenChecked(true);
}, []);
useEffect(() => {
if (!tokenChecked) return;
if (!state.token) {
clearUser();
changeAuthState(false);
@@ -105,7 +109,7 @@ export const AuthProvider = ({ children }) => {
return;
}
getUser();
}, [state.token]);
}, [tokenChecked, state.token]);
return (
<AuthContext.Provider