Skip to content

Commit

Permalink
feat(admin): add admin panel
Browse files Browse the repository at this point in the history
  • Loading branch information
Ibrahimsyah committed Apr 1, 2024
1 parent c0e6865 commit edf03c0
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 123 deletions.
9 changes: 7 additions & 2 deletions src/apis/auth.ts → src/apis/user.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import network from "@/utils/network";
import { AuthRequest, AuthResponse } from "./auth.types";
import { AuthRequest, AuthResponse } from "./user.types";
import { UserInfo } from "@/stores/auh.types";

const getUserInfo = async (): Promise<UserInfo> => network.get('/users/info')
const getUserList = async (): Promise<UserInfo[]> => network.get('/users')
const loginUser = async (request: AuthRequest): Promise<AuthResponse> => network.post('/users/auth', JSON.stringify(request))
const registerUser = async (request: AuthRequest): Promise<AuthResponse> => network.post('/users', JSON.stringify(request))
const updateUser = async (request: UserInfo) => network.put("/users", JSON.stringify(request))

export default {
getUserInfo,
getUserList,
loginUser,
registerUser,
updateUser,

QUERY_KEY_GET_USER_INFO: "getUserInfo"
QUERY_KEY_GET_USER_INFO: "getUserInfo",
QUERY_KEY_GET_USER_LIST: "getUserList"
}
File renamed without changes.
4 changes: 2 additions & 2 deletions src/components/modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const modalTransition: Partial<ModalTransition> = {
exited: { transform: `translateY(100%)` },
}

const Modal = ({title, isOpen, onClose, children, buttonActions }: ModalProps) => {
const Modal = ({ title, isOpen, onClose, children, buttonActions }: ModalProps) => {
return (
<>
<Transition in={isOpen} timeout={modalTransitionDelayMs}>
Expand Down Expand Up @@ -72,7 +72,7 @@ const Modal = ({title, isOpen, onClose, children, buttonActions }: ModalProps) =
key={index}
variant={action.variant}
color={action.color}
onClick={action.onClick}
onClick={action.loading ? () => {} : action.onClick}
loading={action.loading}
>
{action.label}
Expand Down
9 changes: 8 additions & 1 deletion src/pages/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ const settingRoute = createRoute({
loader: () => <></>
}).lazy(() => import('./home/setting').then(d => d.SettingRoute))

const adminRoute = createRoute({
getParentRoute: () => homeRoute,
path: '/admin',
loader: () => <></>
}).lazy(() => import('./home/admin').then(d => d.AdminRoute))


// Auth route section
const authRoute = createRoute({
Expand All @@ -104,7 +110,8 @@ const routeTree = rootRoute.addChildren([
homeRoute.addChildren([
dashboardRoute,
scheduleRoute,
settingRoute
settingRoute,
adminRoute
])
])

Expand Down
10 changes: 5 additions & 5 deletions src/pages/auth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { createLazyRoute, useNavigate } from "@tanstack/react-router"

import icon from '@/assets/icon.png'
import { useEffect, useState } from "react"
import authAPI from '@/apis/auth'
import userAPI from '@/apis/user'
import { useMutation, useQuery } from "@tanstack/react-query"
import { AuthRequest } from "@/apis/auth.types"
import { AuthRequest } from "@/apis/user.types"
import useAuthStore from "@/stores/auth"
import useNotification from "@/stores/notification"

Expand All @@ -25,16 +25,16 @@ const Auth = () => {
const notification = useNotification()

const authMutation = useMutation({
mutationFn: (request: AuthRequest) => isLoginState ? authAPI.loginUser(request) : authAPI.registerUser(request),
mutationFn: (request: AuthRequest) => isLoginState ? userAPI.loginUser(request) : userAPI.registerUser(request),
onSuccess: (data) => {
auth.setAuth(data.token)
}
})

const userInfoQuery = useQuery({
enabled: auth.accessToken != "",
queryFn: authAPI.getUserInfo,
queryKey: [authAPI.QUERY_KEY_GET_USER_INFO]
queryFn: userAPI.getUserInfo,
queryKey: [userAPI.QUERY_KEY_GET_USER_INFO]
})

const handleAuthSubmit = () => {
Expand Down
166 changes: 166 additions & 0 deletions src/pages/home/admin/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import Modal from "@/components/modal"
import { Card, Chip, Grid, Option, Select, Typography } from "@mui/joy"
import { Box, TextField } from "@mui/material"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { createLazyRoute } from "@tanstack/react-router"

import settingAPI from '@/apis/setting'
import userAPI from '@/apis/user'
import { useState } from "react"
import { Actuator } from "@/apis/setting.types"
import { UserInfo } from "@/stores/auh.types"
import useNotification from "@/stores/notification"

type itemState = Actuator | UserInfo | null

const isUserInfo = (item: itemState): boolean => !item || "user_id" in item

const renderModalTitle = (item: itemState): string => {
if (!item) return "Empty"
if (isUserInfo(item)) return "Edit User"
return "Edit Panel"
}

const renderModalBody = (item: itemState, mutator: (item: itemState) => void): JSX.Element => {
if (!item) return <></>
if (isUserInfo(item)) {
const user = item as UserInfo
return (
<Grid container justifyContent='space-between' flexDirection='column' gap={2}>
<TextField label="Name" value={item.name} onChange={e => mutator({ ...item, name: e.target.value })} />
<Select value={user.is_active ? "true" : "false"} onChange={(_, val) => mutator({ ...item, is_active: (val as string) == "true" })}>
<Option key="true" value="true">Active</Option>
<Option key="false" value="false">Not Active</Option>
</Select>
<Select value={user.role} onChange={(_, val) => mutator({ ...item, role: Number(val) })}>
<Option key={2} value={2}>User</Option>
<Option key={99} value={99}>Admin</Option>
</Select>
</Grid>
)
}

const actuator = item as Actuator
return (
<Grid container flexDirection='column' gap={2}>
<TextField label="Name" value={actuator.name} onChange={e => mutator({ ...item, name: e.target.value })} />
<Grid container justifyContent='space-between' direction='row' >
<TextField label="Pin Number" value={actuator.pin_number} onChange={e => mutator({ ...item, pin_number: Number(e.target.value) })} />
<TextField label="Terminal Number" value={actuator.terminal_number} onChange={e => mutator({ ...item, terminal_number: Number(e.target.value) })} />
</Grid>
</Grid >
)
}

const Admin = () => {
const queryClient = useQueryClient()
const notification = useNotification()
const usersQuery = useQuery({
queryKey: [userAPI.QUERY_KEY_GET_USER_LIST],
queryFn: userAPI.getUserList
})

const actuatorsQuery = useQuery({
queryKey: [settingAPI.QUERY_KEY_GET_ACTUATORS],
queryFn: () => settingAPI.getActuators(1)
})

const userMutation = useMutation({
mutationFn: (request: UserInfo) => userAPI.updateUser(request),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [userAPI.QUERY_KEY_GET_USER_LIST] })
notification.fire("User Updated")
resetModal()
}
})

const actuatorMutation = useMutation({
mutationFn: (request: Actuator) => settingAPI.updateActuator(request),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [settingAPI.QUERY_KEY_GET_ACTUATORS] })
notification.fire("Panel Updated")
resetModal()
}
})

const [selectedItem, setSelectedItem] = useState<itemState>(null)
const [isModalOpened, setIsModalOpened] = useState(false)
const resetModal = () => {
setSelectedItem(null)
setIsModalOpened(false)
}

const handleCardClick = (item: Actuator | UserInfo) => {
setSelectedItem(item)
setIsModalOpened(true)
}

const handleUpdateItem = () => {
if (isUserInfo(selectedItem)) {
userMutation.mutate(selectedItem as UserInfo)
return
}

actuatorMutation.mutate(selectedItem as Actuator)
}

return (
<>
<Box sx={{ px: 2 }} className="fade">
<Typography sx={{ mt: 4 }} level="h2" fontWeight='500'>Admin Panel</Typography>
<Typography sx={{ mt: 2 }} level="h4">Users</Typography>
<Grid container sx={{ mt: 1 }} flexDirection='column' gap={2}>
{usersQuery.data?.map(user => (
<Card key={user.user_id} onClick={() => handleCardClick(user)}>
<Box display='flex' justifyContent='space-between'>
<Box>
{user.name}
{user.role == 99 && <Chip sx={{ ml: 2 }} color="danger">Admin</Chip>}
</Box>
<Chip color={user.is_active ? 'success' : 'danger'}>{user.is_active ? "Active" : "Not Active"}</Chip>
</Box>
</Card>
))}
</Grid>
<Typography sx={{ mt: 2 }} level="h4">Panels</Typography>
<Grid container sx={{ mt: 1 }} flexDirection='column' gap={2}>
{actuatorsQuery.data?.map(actuator => (
<Card key={actuator.id} onClick={() => handleCardClick(actuator)}>
<Box display='flex' justifyContent='space-between'>
{actuator.name}
<Grid container gap={1}>
<Chip color="primary">Pin: {actuator.pin_number}</Chip>
<Chip color="warning">{actuator.terminal_number}</Chip>
</Grid>
</Box>
</Card>
))}
</Grid>
</Box >
<Modal
title={renderModalTitle(selectedItem)}
isOpen={!!isModalOpened}
onClose={() => setIsModalOpened(false)}
buttonActions={[
{
label: 'Save',
variant: 'solid',
color: 'primary',
loading: false,
onClick: handleUpdateItem,
},
{
label: 'Cancel',
variant: 'outlined',
color: 'danger',
onClick: () => setIsModalOpened(false)
}
]}
>{renderModalBody(selectedItem, setSelectedItem)}</Modal >
</>
)
}

export const AdminRoute = createLazyRoute('/admin')({
component: Admin
})
6 changes: 3 additions & 3 deletions src/pages/home/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Box, Button, Card, Chip, CircularProgress, Grid, LinearProgress, Link, Typography } from '@mui/joy'
import { useMutation, useQueries, useQuery, useQueryClient } from '@tanstack/react-query'
import actionAPI from '@/apis/action'
import authAPI from '@/apis/auth'
import userAPI from '@/apis/user'
import reportAPI from '@/apis/report'
import { ActionSource, getActionIcon, getActionValueText } from '@/constants/action'
import {
Expand Down Expand Up @@ -44,8 +44,8 @@ const Dashboard = () => {
const { setTab } = useTabStore()

const userInfoQuery = useQuery({
queryFn: authAPI.getUserInfo,
queryKey: [authAPI.QUERY_KEY_GET_USER_INFO]
queryFn: userAPI.getUserInfo,
queryKey: [userAPI.QUERY_KEY_GET_USER_INFO]
})

const actions = useQuery<Action[]>({
Expand Down
18 changes: 18 additions & 0 deletions src/pages/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import ScheduleIcon from '@mui/icons-material/Schedule';
import SettingIcon from '@mui/icons-material/Settings';
import { Outlet, createLazyRoute, useNavigate, useRouterState } from '@tanstack/react-router';
import { Grid } from '@mui/joy';
import { useQuery } from '@tanstack/react-query';
import userAPI from '@/apis/user'
import { UserInfo } from '@/stores/auh.types';

const colors = ['primary', 'danger', 'success', 'warning'] as const;
const tabs = [
Expand Down Expand Up @@ -38,6 +41,10 @@ const App = () => {

const BottomNavigation = () => {
const navigate = useNavigate()
const userInfo = useQuery<UserInfo>({
queryFn: userAPI.getUserInfo,
queryKey: [userAPI.QUERY_KEY_GET_USER_INFO]
})
const currentLocation = useRouterState({
select: state => state.location
})
Expand Down Expand Up @@ -102,6 +109,17 @@ const BottomNavigation = () => {
</ListItemDecorator>
Pengaturan
</Tab>
{userInfo.data?.role == 99 && (
<Tab
orientation="vertical"
{...(currentIndex === 3 && { color: colors[3] })}
>
<ListItemDecorator>
<SettingIcon />
</ListItemDecorator>
Admin
</Tab>
)}
</TabList>
</Tabs>
}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/home/schedule/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import dayjs, { Dayjs } from "dayjs";
import { useCallback, useEffect, useMemo, useState } from "react";
import Modal from './modal';
import Modal from '@/components/modal';
import { Collapse, TextField } from "@mui/material";
import IconButton, { IconButtonProps } from '@mui/joy/IconButton';
import { defaultDateTimeFormat } from "@/constants/date";
import ConfirmationDialog from "../../../components/confirmation-dialog"
import ConfirmationDialog from "@/components/confirmation-dialog"

import settingAPI from "@/apis/setting"
import useTabStore from "@/stores/tab";
Expand Down
Loading

0 comments on commit edf03c0

Please sign in to comment.