Frontend Api integration

This commit is contained in:
jaanvi 2025-02-27 10:06:07 +05:30
parent 74038b9295
commit 292ea123a0
11 changed files with 1701 additions and 308 deletions

View file

@ -21,7 +21,8 @@ interface AddEditCategoryModalProps {
name: string,
email: string,
phone: string,
registeredAddress: string
registeredAddress: string,
password: string
) => void;
editRow: any;
}
@ -31,6 +32,7 @@ interface FormData {
email: string;
phone: string;
registeredAddress: string;
password: string;
}
const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
@ -52,6 +54,7 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
email: "",
phone: "",
registeredAddress: "",
password: "",
},
});
@ -62,7 +65,8 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
data.name,
data.email,
data.phone,
data.registeredAddress
data.registeredAddress,
data.password
);
} else {
handleCreate(data);
@ -168,7 +172,26 @@ const AddEditCategoryModal: React.FC<AddEditCategoryModalProps> = ({
/>
)}
/>
<Controller
name="password"
control={control}
rules={{
required: "password is required",
}}
render={({ field }) => (
<TextField
{...field}
required
margin="dense"
label="Password"
type="password"
fullWidth
variant="standard"
error={!!errors.password}
helperText={errors.password?.message}
/>
)}
/>
<Controller
name="phone"
control={control}

View file

@ -0,0 +1,300 @@
import React, { useEffect } from "react";
import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
FormControlLabel,
FormHelperText,
FormLabel,
InputLabel,
MenuItem,
Select,
TextField,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useForm, Controller } from "react-hook-form";
interface AddRoleModalProps {
open: boolean;
handleClose: () => void;
handleCreate: (data: FormData) => void;
handleUpdate: (
id: string,
name: string,
resource: {
moduleName: string;
moduleId: string;
permissions: string[];
}[]
) => void;
editRow: any;
data: {
resource: {
moduleName: string;
moduleId: string;
permissions: string[];
}[];
}; // Assuming `data` is passed as a prop
}
interface FormData {
name: string;
resource: {
moduleName: string;
moduleId: string;
permissions: string[];
}[];
}
const AddRoleModal: React.FC<AddRoleModalProps> = ({
open,
handleClose,
handleCreate,
handleUpdate,
editRow,
data,
}) => {
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
getValues, // Access getValues from the form methods here
} = useForm<FormData>({
defaultValues: {
name: "",
resource: [], // Ensure resource is initialized as an empty array
},
});
useEffect(() => {
if (editRow) {
setValue("name", editRow.name);
setValue("resource", editRow.resource);
}
}, [editRow, setValue]);
// Handles permissions checkbox change for a specific resource
const handlePermissionChange = (
resourceIndex: number,
permission: string,
checked: boolean
) => {
const updatedResources = [...getValues().resource]; // Use getValues to get the current form values
const resource = updatedResources[resourceIndex];
if (checked) {
// Add permission if checked
resource.permissions = [
...new Set([...resource.permissions, permission]),
];
} else {
// Remove permission if unchecked
resource.permissions = resource.permissions.filter(
(p) => p !== permission
);
}
setValue("resource", updatedResources); // Update the resource field in form state
};
const onSubmit = (data: FormData) => {
if (editRow) {
handleUpdate(editRow.id, data.name, data.resource);
} else {
handleCreate(data);
}
handleClose();
reset();
};
return (
<Dialog
open={open}
onClose={handleClose}
maxWidth="md"
fullWidth
PaperProps={{
component: "form",
onSubmit: handleSubmit(onSubmit),
}}
>
<DialogTitle
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
{editRow ? "Edit Role" : "Add Role"}
<Box
onClick={handleClose}
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
>
<CloseIcon />
</Box>
</DialogTitle>
<DialogContent>
{/* Role Name Field */}
<Controller
name="name"
control={control}
rules={{
required: "Role Name is required",
minLength: {
value: 3,
message: "Minimum 3 characters required",
},
maxLength: {
value: 30,
message: "Maximum 30 characters allowed",
},
}}
render={({ field }) => (
<TextField
{...field}
autoFocus
required
margin="dense"
label="Role Name"
type="text"
fullWidth
variant="standard"
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
{/* Resource Field */}
<Controller
name="resource"
control={control}
rules={{ required: "Resource is required" }}
render={({ field }) => (
<FormControl
fullWidth
margin="dense"
error={!!errors.resource}
>
<InputLabel>Resource</InputLabel>
<Select
{...field}
label="Resource"
required
fullWidth
variant="standard"
>
{/* Mapping over the resource array to display the options */}
{data.resource?.map((resourceItem, index) => (
<MenuItem
key={index}
value={resourceItem.moduleId}
>
{resourceItem.moduleName}
</MenuItem>
))}
</Select>
<FormHelperText>
{errors.resource?.message}
</FormHelperText>
</FormControl>
)}
/>
{/* Permissions Checkbox Fields for each resource */}
{getValues().resource &&
getValues().resource.length > 0 &&
getValues().resource.map((resource, resourceIndex) => (
<React.Fragment key={resourceIndex}>
<FormControl fullWidth margin="dense">
<FormLabel>
{resource.moduleName} Permissions
</FormLabel>
<FormControlLabel
control={
<Checkbox
value="view"
checked={resource.permissions.includes(
"view"
)}
onChange={(e) =>
handlePermissionChange(
resourceIndex,
"view",
e.target.checked
)
}
/>
}
label="View"
/>
<FormControlLabel
control={
<Checkbox
value="edit"
checked={resource.permissions.includes(
"edit"
)}
onChange={(e) =>
handlePermissionChange(
resourceIndex,
"edit",
e.target.checked
)
}
/>
}
label="Edit"
/>
<FormControlLabel
control={
<Checkbox
value="delete"
checked={resource.permissions.includes(
"delete"
)}
onChange={(e) =>
handlePermissionChange(
resourceIndex,
"delete",
e.target.checked
)
}
/>
}
label="Delete"
/>
<FormHelperText>
{
errors.resource?.[resourceIndex]
?.permissions?.message
}
</FormHelperText>
</FormControl>
</React.Fragment>
))}
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button type="submit">{editRow ? "Update" : "Create"}</Button>
</DialogActions>
</Dialog>
);
};
export default AddRoleModal;

View file

@ -0,0 +1,212 @@
import React, { useEffect } from "react";
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useForm, Controller } from "react-hook-form";
//By Jaanvi : Edit Model :: 11-feb-25
interface AddUserModalProps {
open: boolean;
handleClose: () => void;
handleCreate: (data: FormData) => void;
handleUpdate: (
id: string,
name: string,
email: string,
phone: string,
password: string
) => void;
editRow: any;
}
interface FormData {
name: string;
email: string;
phone: string;
password: string;
}
const AddUserModal: React.FC<AddUserModalProps> = ({
open,
handleClose,
handleCreate,
editRow,
}) => {
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
} = useForm<FormData>({
defaultValues: {
name: "",
email: "",
phone: "",
password: "",
},
});
const onSubmit = (data: FormData) => {
handleCreate(data);
handleClose();
reset();
};
return (
<Dialog
open={open}
onClose={handleClose}
maxWidth="md"
fullWidth
PaperProps={{
component: "form",
onSubmit: handleSubmit(onSubmit),
}}
>
<DialogTitle
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
{editRow ? "Edit Admin" : "Add Admin"}
<Box
onClick={handleClose}
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
}}
>
<CloseIcon />
</Box>
</DialogTitle>
<DialogContent>
<Controller
name="name"
control={control}
rules={{
required: "Admin Name is required",
minLength: {
value: 3,
message: "Minimum 3 characters required",
},
maxLength: {
value: 30,
message: "Maximum 30 characters allowed",
},
}}
render={({ field }) => (
<TextField
{...field}
autoFocus
required
margin="dense"
label="User Name"
type="text"
fullWidth
variant="standard"
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
<Controller
name="email"
control={control}
rules={{
required: "Email is required",
pattern: {
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: "Invalid email address",
},
}}
render={({ field }) => (
<TextField
{...field}
required
margin="dense"
label="Email"
type="email"
fullWidth
variant="standard"
error={!!errors.email}
helperText={errors.email?.message}
/>
)}
/>
<Controller
name="password"
control={control}
rules={{
required: "password is required",
}}
render={({ field }) => (
<TextField
{...field}
required
margin="dense"
label="Password"
type="password"
fullWidth
variant="standard"
error={!!errors.password}
helperText={errors.password?.message}
/>
)}
/>
<Controller
name="phone"
control={control}
rules={{
required: "Phone number is required",
pattern: {
value: /^[0-9]*$/,
message: "Only numbers are allowed",
},
minLength: {
value: 6,
message: "Phone number must be exactly 6 digits",
},
maxLength: {
value: 14,
message: "Phone number must be exactly 14 digits",
},
}}
render={({ field }) => (
<TextField
{...field}
required
margin="dense"
label="Phone Number"
type="tel"
fullWidth
variant="standard"
error={!!errors.phone}
helperText={errors.phone?.message}
/>
)}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button type="submit">Create</Button>
</DialogActions>
</Dialog>
);
};
export default AddUserModal;

View file

@ -29,6 +29,11 @@ const baseMenuItems = [
icon: <AnalyticsRoundedIcon />,
url: "/panel/user-list",
},
{
text: "Roles",
icon: <AnalyticsRoundedIcon />,
url: "/panel/role-list",
},
];
//Eknoor singh and Jaanvi
@ -44,7 +49,7 @@ type PropType = {
export default function MenuContent({ hidden }: PropType) {
const location = useLocation();
const userRole = useSelector(
(state: RootState) => state.profileReducer.user?.role
(state: RootState) => state.profileReducer.user?.userType
);

View file

@ -0,0 +1,137 @@
import React, { useEffect, useState } from "react";
import { Box, Button, Typography } from "@mui/material";
import AddEditRoleModal from "../../components/AddEditRoleModal";
import { useForm } from "react-hook-form";
import CustomTable, { Column } from "../../components/CustomTable";
import { useDispatch, useSelector } from "react-redux";
import { createRole, roleList } from "../../redux/slices/roleSlice";
import { AppDispatch, RootState } from "../../redux/store/store";
export default function RoleList() {
const [modalOpen, setModalOpen] = useState(false);
const { reset } = useForm();
const [deleteModal, setDeleteModal] = React.useState<boolean>(false);
const [viewModal, setViewModal] = React.useState<boolean>(false);
const [rowData, setRowData] = React.useState<any | null>(null);
const dispatch = useDispatch<AppDispatch>();
const roles = useSelector((state: RootState) => state.roleReducer.roles);
useEffect(() => {
dispatch(roleList());
}, [dispatch]);
const handleClickOpen = () => {
setRowData(null); // Reset row data when opening for new role
setModalOpen(true);
};
const handleCloseModal = () => {
setModalOpen(false);
setRowData(null);
reset();
};
const handleCreate = async (data: {
name: string;
resource: {
moduleName: string;
moduleId: string;
permissions: string[];
}[];
}) => {
try {
await dispatch(createRole(data));
await dispatch(roleList()); // Refresh the list after creation
handleCloseModal();
} catch (error) {
console.error("Creation failed", error);
}
};
const categoryColumns: Column[] = [
{ id: "srno", label: "Sr No" },
{ id: "name", label: "Name" },
{ id: "action", label: "Action", align: "center" },
];
const categoryRows = roles?.length
? roles?.map(function (
role: {
id: string;
name: string;
// email: string;
// phone: string;
// location?: string;
// managerAssigned?: string;
// vehicle?: string;
},
index: number
) {
return {
id: role?.id,
srno: index + 1,
name: role?.name,
// email: user?.email,
// phone: user?.phone,
// location: user?.location,
// managerAssigned: user?.managerAssigned,
// vehicle: user?.vehicle,
};
})
: [];
console.log("Category Rows:", categoryRows);
return (
<>
<Box
sx={{
width: "100%",
maxWidth: {
sm: "100%",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
},
}}
>
<Typography
component="h2"
variant="h6"
sx={{ mt: 2, fontWeight: 600 }}
>
Roles
</Typography>
<Button
variant="contained"
size="medium"
sx={{ textAlign: "right" }}
onClick={handleClickOpen}
>
Add Role
</Button>
</Box>
<CustomTable
columns={categoryColumns}
rows={categoryRows}
setDeleteModal={setDeleteModal}
deleteModal={deleteModal}
setViewModal={setViewModal}
viewModal={viewModal}
setRowData={setRowData}
setModalOpen={setModalOpen}
/>
<AddEditRoleModal
open={modalOpen}
handleClose={handleCloseModal}
handleCreate={handleCreate}
editRow={rowData}
/>
</>
);
}

File diff suppressed because it is too large Load diff

View file

@ -4,12 +4,14 @@ import authReducer from "./slices/authSlice";
import adminReducer from "./slices/adminSlice";
import profileReducer from "./slices/profileSlice";
import userReducer from "./slices/userSlice.ts";
import roleReducer from "./slices/roleSlice.ts";
const rootReducer = combineReducers({
authReducer,
adminReducer,
profileReducer,
userReducer,
authReducer,
adminReducer,
profileReducer,
userReducer,
roleReducer,
});
export type RootState = ReturnType<typeof rootReducer>;

View file

@ -7,7 +7,7 @@ interface User {
id: string;
name: string;
email: string;
role: string;
userType: string;
phone: string;
}

View file

@ -0,0 +1,115 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import axios from "axios";
import http from "../../lib/https";
import { toast } from "sonner";
// Define TypeScript types
interface Role {
id: any;
name: string;
resource: {
moduleName: string;
moduleId: string;
permissions: string[];
}[];
}
interface RoleState {
roles: Role[];
loading: boolean;
error: string | null;
}
// Initial state
const initialState: RoleState = {
roles: [],
loading: false,
error: null,
};
export const roleList = createAsyncThunk<Role[], void, { rejectValue: string }>(
"fetchRoles",
async (_, { rejectWithValue }) => {
try {
const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
const response = await http.get("get");
if (!response.data?.data) throw new Error("Invalid API response");
return response.data.data;
} catch (error: any) {
toast.error("Error Fetching Roles" + error);
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
}
);
// Create Role
export const createRole = createAsyncThunk<
Role,
{
name: string;
resource: {
moduleName: string;
moduleId: string;
permissions: string[];
}[];
},
{ rejectValue: string }
>("/CreateRole", async (data, { rejectWithValue }) => {
try {
const response = await http.post("create", data);
return response.data;
} catch (error: any) {
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
const roleSlice = createSlice({
name: "fetchRoles",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(roleList.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(
roleList.fulfilled,
(state, action: PayloadAction<any>) => {
state.loading = false;
state.roles = action.payload.results; // Extract results from response
}
)
.addCase(roleList.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || "Failed to fetch roles";
})
.addCase(createRole.pending, (state) => {
state.loading = true;
})
.addCase(
createRole.fulfilled,
(state, action: PayloadAction<Role>) => {
state.loading = false;
state.roles.push(action.payload);
}
)
.addCase(
createRole.rejected,
(state, action: PayloadAction<string | undefined>) => {
state.loading = false;
state.error = action.payload || "Failed to create role";
}
);
},
});
export default roleSlice.reducer;

View file

@ -1,5 +1,7 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import axios from "axios";
import http from "../../lib/https";
import { toast } from "sonner";
// Define TypeScript types
interface User {
@ -7,9 +9,10 @@ interface User {
name: string;
email: string;
phone?: string;
location?: string;
managerAssigned?: string;
vehicle?: string;
// location?: string;
// managerAssigned?: string;
// vehicle?: string;
password:string;
}
interface UserState {
@ -26,17 +29,62 @@ const initialState: UserState = {
};
// Async thunk to fetch user list
export const userList = createAsyncThunk<User[]>("users/fetchUsers", async () => {
// export const userList = createAsyncThunk<User[]>("users/fetchUsers", async () => {
// try {
// const response = await axios.get<User[]>("/api/users"); // Adjust the API endpoint as needed
// return response.data;
// } catch (error: any) {
// throw new Error(error.response?.data?.message || "Failed to fetch users");
// }
// });
export const userList = createAsyncThunk<User, void, { rejectValue: string }>(
"fetchUsers",
async (_, { rejectWithValue }) => {
try {
const token = localStorage?.getItem("authToken");
if (!token) throw new Error("No token found");
const response = await http.get("users-list");
if (!response.data?.data) throw new Error("Invalid API response");
return response.data.data;
} catch (error: any) {
toast.error("Error Fetching Profile" + error);
return rejectWithValue(
error?.response?.data?.message || "An error occurred"
);
}
}
);
//Create User
export const createUser = createAsyncThunk<
User,
{
name: string;
email: string;
password: string;
phone: string;
// location?: string;
// managerAssigned?: string;
// vehicle?: string;
},
{ rejectValue: string }
>("/CreateUser", async (data, { rejectWithValue }) => {
try {
const response = await axios.get<User[]>("/api/users"); // Adjust the API endpoint as needed
const response = await http.post("create-user", data);
return response.data;
} catch (error: any) {
throw new Error(error.response?.data?.message || "Failed to fetch users");
return rejectWithValue(
error.response?.data?.message || "An error occurred"
);
}
});
const userSlice = createSlice({
name: "users",
name: "fetchUsers",
initialState,
reducers: {},
extraReducers: (builder) => {
@ -45,14 +93,34 @@ const userSlice = createSlice({
state.loading = true;
state.error = null;
})
.addCase(userList.fulfilled, (state, action: PayloadAction<User[]>) => {
state.loading = false;
state.users = action.payload;
})
.addCase(
userList.fulfilled,
(state, action: PayloadAction<User[]>) => {
state.loading = false;
state.users = action.payload;
}
)
.addCase(userList.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message || "Failed to fetch users";
});
})
.addCase(createUser.pending, (state) => {
state.loading = true;
// state.error = null;
})
.addCase(
createUser.fulfilled,
(state, action: PayloadAction<User>) => {
state.loading = false;
state.users.push(action.payload);
}
)
.addCase(
createUser.rejected,
(state, action: PayloadAction<string | undefined>) => {
state.loading = false;
}
);
},
});

View file

@ -2,6 +2,7 @@ import { Routes as BaseRoutes, Navigate, Route } from "react-router-dom";
import React, { lazy, Suspense } from "react";
import LoadingComponent from "./components/Loading";
import DashboardLayout from "./layouts/DashboardLayout";
import RoleList from "./pages/RoleList";
// Page imports
const Login = lazy(() => import("./pages/Auth/Login"));
@ -83,6 +84,15 @@ export default function AppRouter() {
/>
}
/>
<Route
path="role-list"
element={
<ProtectedRoute
caps={[]}
component={<RoleList />}
/>
}
/>
<Route
path="profile"