add favicon, debounce, categories done

dev
Aji Kamaludin 3 years ago
parent 13f332f8a5
commit 614a0630fd
No known key found for this signature in database
GPG Key ID: 670E1F26AD5A8099

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@ -9,6 +9,8 @@ import {
Route,
BrowserRouter,
} from 'react-router-dom'
import { SWRConfig } from "swr";
import axios from "axios";
import "react-datepicker/dist/react-datepicker.css";
import "@fontsource/raleway/400.css"
import "@fontsource/open-sans/700.css"
@ -25,7 +27,10 @@ const Login = React.lazy(() => import('./views/auth/Login'))
const Register = React.lazy(() => import('./views/auth/Register'))
const Dashboard = React.lazy(() => import('./layouts/Dashboard'))
// font awesome
library.add(fas)
// chakra ui custom theme
const customTheme = extendTheme(withDefaultColorScheme(
{
colorScheme: "red",
@ -36,25 +41,48 @@ const customTheme = extendTheme(withDefaultColorScheme(
}
))
// config fatcher for swr with axios
const fetcher = (url, token) => axios({
method: "GET",
url: url,
headers: {
'Authorization': `Bearer ${token}`
}
}).then(res => {
return res.data.data
})
class App extends React.Component {
render() {
return (
<AppProvider>
<BrowserRouter>
<ChakraProvider theme={customTheme}>
<ErrorBoundary>
<React.Suspense fallback={<Loading/>}>
<Switch>
<Route path="/login" exect={true} render={(props) => <Login {...props} />}/>
<Route path="/register" exect={true} render={(props) => <Register {...props} />}/>
<Route path="/error" exect={true} render={(props) => <AppCrash {...props} />}/>
<Route path="/" render={(props) => <Dashboard {...props} />}/>
<Route path="*" render={(props) => <NotFound/>}/>
</Switch>
</React.Suspense>
</ErrorBoundary>
</ChakraProvider>
</BrowserRouter>
// handle global value
<AppProvider>
{/* handle useSwr to fetch api */}
<SWRConfig
value={{
refreshInterval: 60000,
fetcher
}}
>
{/* react router dom */}
<BrowserRouter>
{/* handle chakra ui */}
<ChakraProvider theme={customTheme}>
{/* handle app crash */}
<ErrorBoundary>
<React.Suspense fallback={<Loading/>}>
<Switch>
<Route path="/login" exect={true} render={(props) => <Login {...props} />}/>
<Route path="/register" exect={true} render={(props) => <Register {...props} />}/>
<Route path="/error" exect={true} render={(props) => <AppCrash {...props} />}/>
<Route path="/" render={(props) => <Dashboard {...props} />}/>
<Route path="*" render={(props) => <NotFound/>}/>
</Switch>
</React.Suspense>
</ErrorBoundary>
</ChakraProvider>
</BrowserRouter>
</SWRConfig>
</AppProvider>
);
}

@ -1,23 +1,9 @@
import axios from "axios"
import useSWR from "swr"
import { useAuth } from "../context/AppContext"
import { formatDate } from "../utils"
const fetcher = (url, token) => axios({
method: "GET",
url: url,
headers: {
'Authorization': `Bearer ${token}`
}
}).then(res => {
return res.data.data
})
export function useProducts({startDate, endDate}) {
const { user } = useAuth()
export function useProducts(user, { page = 1, q = '' }) {
const { data, error } = useSWR([
`/products?startDate=${formatDate(startDate)}&endDate=${formatDate(endDate)}`, user.accessToken
], fetcher)
`/products?page=${page}&q=${q}`, user.accessToken
])
return [
data,
@ -25,6 +11,13 @@ export function useProducts({startDate, endDate}) {
]
}
export function useProduct(id) {
export function useCategories(user, { page = 1, q }) {
const { data, error } = useSWR([
`/categories?page=${page}&q=${q}`, user.accessToken
])
}
return [
data,
error
]
}

@ -1,4 +1,4 @@
import { useRef } from "react"
import { useRef, useState } from "react"
import {
Button,
AlertDialog,
@ -11,6 +11,7 @@ import {
export default function Alert({ isOpen, onClose, toggle }) {
const cancelRef = useRef()
const [loading, setLoading] = useState(false)
return (
<>
@ -22,18 +23,27 @@ export default function Alert({ isOpen, onClose, toggle }) {
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
Delete Customer
hapus
</AlertDialogHeader>
<AlertDialogBody>
Are you sure? You can't undo this action afterwards.
anda yakin akan menghapus ?
</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={toggle}>
Cancel
<Button bg="white" color="black" _hover={{bg: "gray.200"}} _active={{bg: "gray.200"}} ref={cancelRef} onClick={toggle}>
batal
</Button>
<Button colorScheme="red" onClick={toggle} ml={3}>
<Button
onClick={async () => {
setLoading(true)
await onClose()
toggle()
setLoading(false)
}}
ml={3}
isLoading={loading}
>
Delete
</Button>
</AlertDialogFooter>

@ -0,0 +1,18 @@
import { Box, Heading } from '@chakra-ui/react'
import { Link } from 'react-router-dom'
export default function Breadcrumb({ main: [link, menu, submenu]}) {
return (
<Box p="3" m="2" bg="white" rounded="lg">
<Heading size="md">
<Link to="/dashboard">
dashboard {' '}
</Link>
<Link to={link}>
/ {menu} {' '}
</Link>
{submenu && ` / ${submenu}`}
</Heading>
</Box>
)
}

@ -0,0 +1,17 @@
import { useState } from "react"
export function useModalState(init = false) {
const [isOpen, setIsOpen] = useState(init)
const [val, setVal] = useState({})
const toggle = (data = {}) => {
setIsOpen(!isOpen)
setVal(data)
}
return {
isOpen,
toggle,
selected: val
}
}

@ -0,0 +1,74 @@
import { useEffect, useState } from "react";
import { FormControl, FormLabel, Input, Box, InputGroup, InputRightElement } from "@chakra-ui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import NumberFormat from "react-number-format";
export function SearchInput({ setter: [search, setSearch] }) {
return (
<Box px="3" mt="6">
<InputGroup>
<Input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="cari"
focusBorderColor="red.500"
/>
<InputRightElement children={<FontAwesomeIcon icon="search" />} />
</InputGroup>
</Box>
)
}
export function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
},
[value, delay]
);
return debouncedValue;
}
export function FormInput(props) {
const { data: [name, val, setVal = () => {}, type = "text"]} = props
return (
<FormControl id={name} mb="3">
<FormLabel fontWeight="bold">{name}</FormLabel>
<Input
focusBorderColor="red.500"
type={type}
value={val}
onChange={e => setVal(e.target.value)}
{...props}
/>
</FormControl>
)
}
export function FormInputNumber(props) {
const { data:[name, val, setVal] } = props
return (
<FormControl id={name} mb="3">
<FormLabel fontWeight="bold">{name}</FormLabel>
<NumberFormat
customInput={Input}
thousandSeparator="."
decimalSeparator=","
allowNegative={false}
allowLeadingZeros={false}
style={{textAlign: "right"}}
value={val}
onValueChange={e => setVal(e.value)}
focusBorderColor="red.500"
{...props}
/>
</FormControl>
)
}

@ -1,17 +1,36 @@
import { HStack, Box } from "@chakra-ui/layout"
export function usePaginate() {}
export function Pagination() {
function StyledBox(props) {
return (
<Box
px="3"
py="1"
outlineColor="red.700"
rounded="3xl"
color="white"
{...props}
>
{props.children}
</Box>
)
}
export function Pagination({ page = 1, setPage, totalPages = 1}) {
return (
<HStack spacing="2" mt="3" justifyContent="flex-end">
<Box px="3" py="1" bg="red.200" outlineColor="red.700" rounded="3xl" color="white" _hover={{ bg: "red:700" }}>
<StyledBox
bg={page === 1 ? "red.200" : "red.500"}
onClick={() => setPage(+page - 1 <= 0 ? 1 : +page - 1)}
>
{"<"}
</Box>
<Box px="3" py="1" bg="red.500" outlineColor="red.700" rounded="3xl" color="white" _hover={{ bg: "red:700" }}>
1
</Box>
<Box px="3" py="1" bg="red.500" outlineColor="red.700" rounded="3xl" color="white" _hover={{ bg: "red:700" }}>
</StyledBox>
<StyledBox
bg={page === (+totalPages > 0 ? +totalPages : 1) ? "red.200" : "red.500"}
onClick={() => setPage(+page + 1 >= +totalPages ? +totalPages : +page + 1)}
>
&gt;
</Box>
</StyledBox>
</HStack>
)
}

@ -0,0 +1,8 @@
export { default as Breadcrumb } from "./Breadcrumb"
export { default as Card } from "./Card"
export { default as Loading } from "./Loading"
export { default as AlertDialog } from "./Alert"
export * from "./DatePickerFilter"
export * from "./Pagination"
export * from "./Input"
export * from "./Hooks"

@ -1,11 +1,13 @@
import React from "react";
const Dashboard = React.lazy(() => import("../views/dashboard"));
const ListProduct = React.lazy(() => import("../views/products"));
const Product = React.lazy(() => import("../views/products"));
const Categories = React.lazy(() => import("../views/categories"));
const routes = [
{ path: '/', exact: true, name: 'Home' },
{ path: "/dashboard", name: "dashboard", component: Dashboard },
{ path: "/products", name: "product", component: ListProduct }
{ path: "/products", name: "produk", component: Product },
{ path: "/categories", name: "kategori", component: Categories }
]
export default routes;

@ -0,0 +1,59 @@
import axios from "axios";
export function createCategory(payload, token) {
return axios({
method: 'POST',
url: '/categories',
data: payload,
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => res.data)
.catch(err => {
throw err.response.data
})
}
export function getCategory(id, token){
return axios({
method: 'GET',
url: `/categories/${id}`,
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => res.data.data)
.catch(err => {
throw err.response.data
})
}
export function updateCategory(id, payload, token) {
return axios({
method: 'PUT',
url: `/categories/${id}`,
data: payload,
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => res.data)
.catch(err => {
throw err.response.data
})
}
export function deleteCategory(id, token) {
return axios({
method: 'DELETE',
url: `/categories/${id}`,
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => res.data)
.catch(err => {
throw err.response.data
})
}

@ -0,0 +1,66 @@
import {
Button,
Alert,
AlertIcon,
useToast
} from "@chakra-ui/react"
import { createCategory } from "./Api"
import { useState } from "react"
import { Breadcrumb, Card, FormInput } from "../../components/Common"
import { useAuth } from "../../context/AppContext"
export default function Create({ history }) {
const { user } = useAuth()
const toast = useToast()
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const [submit, setSubmit] = useState(false)
const [error, setError] = useState(null)
const handleSubmit = () => {
setSubmit(true)
createCategory({ name, description: description ? description : '' }, user.accessToken)
.then(res => {
toast({
title: res.status,
description: "item ditambahkan",
status: "success",
duration: 4000,
isClosable: true,
position: "top-right"
})
history.push('/categories')
})
.catch(err => {
setError(err.message)
})
.finally(() => {
setSubmit(false)
})
}
return (
<>
<Breadcrumb main={["/categories", "kategori", "baru"]}/>
<Card>
{error !== null && (
<Alert status="error" mb="5" rounded="md">
<AlertIcon />
{error}
</Alert>
)}
<FormInput data={['nama', name, setName]}/>
<FormInput data={['deskripsi', description, setDescription]}/>
<Button
mt="4"
isLoading={submit}
onClick={() => handleSubmit()}
>
simpan
</Button>
</Card>
</>
)
}

@ -0,0 +1,92 @@
import {
Button,
Alert,
AlertIcon,
useToast
} from "@chakra-ui/react"
import { getCategory, updateCategory } from "./Api"
import { useState } from "react"
import { Breadcrumb, Card, FormInput, Loading } from "../../components/Common"
import { useAuth } from "../../context/AppContext"
import { useEffect } from "react"
export default function Edit(props) {
const id = props.match.params.id
const { user } = useAuth()
const toast = useToast()
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const [loading, setLoading] = useState(false)
const [submit, setSubmit] = useState(false)
const [error, setError] = useState(null)
const handleSubmit = () => {
setSubmit(true)
updateCategory(id, {
name,
description: description ? description : ''
}, user.accessToken)
.then(res => {
toast({
title: res.status,
description: "item diubah",
status: "success",
duration: 4000,
isClosable: true,
position: "top-right"
})
props.history.push('/categories')
})
.catch(err => {
setError(err.message)
})
.finally(() => {
setSubmit(false)
})
}
useEffect(() => {
const { accessToken: token } = user
setLoading(true)
getCategory(id, token)
.then(res => {
setName(res.category.name)
setDescription(res.category.description)
})
.catch(err => setError(err.message))
.finally(() => setLoading(false))
return () => {}
}, [id, user])
return (
<>
<Breadcrumb main={["/categories", "kategori", "ubah"]}/>
<Card>
{error !== null && (
<Alert status="error" mb="5" rounded="md">
<AlertIcon />
{error}
</Alert>
)}
{loading ? (
<Loading/>
) : (
<>
<FormInput data={['nama', name, setName]}/>
<FormInput data={['deskripsi', description, setDescription]}/>
<Button
mt="4"
isLoading={submit}
onClick={() => handleSubmit()}
>
simpan
</Button>
</>
)}
</Card>
</>
)
}

@ -0,0 +1,138 @@
import { useState } from "react"
import {
Button,
Alert,
AlertIcon,
Table,
Thead,
Tr,
Td,
Th,
Tbody,
Menu,
MenuItem,
MenuButton,
MenuList,
useToast,
} from "@chakra-ui/react"
import { Link } from "react-router-dom"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { mutate } from 'swr'
import {
Breadcrumb,
Card,
Loading,
Pagination,
useDebounce,
AlertDialog,
SearchInput,
useModalState,
} from "../../components/Common"
import { useCategories } from "../../api"
import { deleteCategory } from "./Api"
import { useAuth } from "../../context/AppContext"
export default function List() {
const { user } = useAuth()
const toast = useToast()
const [page, setPage] = useState(1)
const [search, setSearch] = useState('')
const q = useDebounce(search, 300)
const [data, error] = useCategories(user, { page, q })
const {isOpen, toggle, selected} = useModalState(false)
const handleDelete = async () => {
await deleteCategory(selected.id, user.accessToken)
.then((res) => {
toast({
title: res.status,
description: "item dihapus",
status: "success",
position: "top-right",
duration: 4000,
isClosable: true
})
mutate([`/categories?page=${page}&q=${q}`, user.accessToken])
})
.catch((err) => {
toast({
title: err.status,
description: err.message,
status: "error",
position: "top-right",
duration: 4000,
isClosable: true
})
})
}
if(error) {
return (
<Alert status="error">
<AlertIcon />
{error.message}
</Alert>
)
}
return (
<>
<Breadcrumb main={["/categories", "kategori"]}/>
<Card>
<Button as={Link} to="/categories/create" size="md">
tambah
</Button>
<SearchInput setter={[search, setSearch]}/>
{data ? (
<>
<Table variant="simple" mt="2" mb="4">
<Thead>
<Tr>
<Th>nama</Th>
<Th>deskripsi</Th>
<Th isNumeric>{''}</Th>
</Tr>
</Thead>
<Tbody>
{data.categories.map((category) => (
<Tr key={category.id}>
<Td>{ category.name }</Td>
<Td>{ category.description }</Td>
<Td isNumeric>
<Menu>
<MenuButton as={Button}>
<FontAwesomeIcon icon="ellipsis-v"/>
</MenuButton>
<MenuList>
<MenuItem as={Link} to={`/categories/${category.id}/edit`}>ubah</MenuItem>
<MenuItem
onClick={() => {
toggle(category)
}}
>
hapus
</MenuItem>
</MenuList>
</Menu>
</Td>
</Tr>
))}
</Tbody>
</Table>
<Pagination page={page} setPage={setPage} totalPages={data.meta.totalPages}/>
</>
) : (
<Loading/>
)}
</Card>
<AlertDialog
isOpen={isOpen}
onClose={handleDelete}
toggle={() => toggle()}
/>
</>
)
}

@ -0,0 +1,17 @@
import { Switch, Route } from 'react-router-dom'
import Create from './Create'
import List from './List'
import Edit from './Edit'
function routes(props) {
return (
<Switch>
<Route path="/categories/:id/edit" exect component={Edit} />
<Route path="/categories/create" exact component={Create} />
<Route path="/categories" exact component={List} />
</Switch>
)
}
export default routes

@ -0,0 +1,29 @@
import {
Button,
} from "@chakra-ui/react"
import { useState } from "react"
import { Breadcrumb, Card, FormInput, FormInputNumber } from "../../components/Common"
export default function Create() {
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const [cost, setCost] = useState(0)
const [price, setPrice] = useState(0)
const [stock, setStock] = useState(0)
const [category, setCategory] = useState({})
return (
<>
<Breadcrumb main={["/products", "produk", "baru"]}/>
<Card>
<FormInput data={['nama', name, setName]}/>
<FormInput data={['deskripsi', description, setDescription]}/>
<FormInputNumber data={['harga beli', cost, setCost]} onClick={() => setCost('')}/>
<FormInputNumber data={['harga jual', price, setPrice]} onClick={() => setPrice('')}/>
<FormInputNumber data={['stok', stock, setStock]}/>
<FormInput data={['kategori', category ? category.name : '']} readOnly={true}/>
<Button mt="4">simpan</Button>
</Card>
</>
)
}

@ -1,25 +1,34 @@
import { useState } from "react"
import {
Button,
Alert,
AlertIcon,
Box,
Table,
Thead,
Tr,
Td,
Th,
Tbody,
Heading
} from "@chakra-ui/react"
import { useProducts } from "../../api"
import Card from "../../components/Common/Card"
import Loading from "../../components/Common/Loading"
import { DatePickerFilter, useDatePickerFilter } from "../../components/Common/DatePickerFilter"
import {
Breadcrumb,
Card,
Loading,
SearchInput,
Pagination,
useDebounce
} from "../../components/Common"
import { formatIDR } from "../../utils"
import { Link } from "react-router-dom"
import { useAuth } from "../../context/AppContext"
export default function List({ history }) {
const [ startDate, endDate, setter ] = useDatePickerFilter()
const [ data, error ] = useProducts({startDate, endDate})
const { user } = useAuth()
const [page, setPage] = useState(1)
const [search, setSearch] = useState('')
const q = useDebounce(search, 600)
const [ data, error ] = useProducts(user, { page, q })
const handleItemClick = (id) => {
history.push(`/products/${id}`)
@ -36,13 +45,14 @@ export default function List({ history }) {
return (
<>
<Box p="3" m="2" bg="white" rounded="lg">
<Heading size="md">dashboard / produk</Heading>
</Box>
<Breadcrumb main={["/products", "produk"]}/>
<Card>
<Button size="md">tambah</Button>
<DatePickerFilter startDate={startDate} endDate={endDate} setter={setter} />
<Button as={Link} to="/products/create" size="md">
tambah
</Button>
<SearchInput setter={[search, setSearch]}/>
{data ? (
<>
<Table variant="simple" mt="2" mb="4">
<Thead>
<Tr>
@ -63,6 +73,8 @@ export default function List({ history }) {
))}
</Tbody>
</Table>
<Pagination page={page} setPage={setPage} totalPages={data.meta.totalPages}/>
</>
) : (
<Loading/>
)}

@ -1,15 +1,17 @@
import { Switch, Route } from 'react-router-dom'
import ProductList from './List'
import ProductDetail from './Detail'
import Create from './Create'
import List from './List'
import Detail from './Detail'
function ProductRoutes(props) {
function routes(props) {
return (
<Switch>
<Route path="/products/:id" exact component={ProductDetail} />
<Route path="/products" exact component={ProductList} />
<Route path="/products/create" exact component={Create} />
<Route path="/products" exact component={List} />
<Route path="/products/:id" exact component={Detail} />
</Switch>
)
}
export default ProductRoutes
export default routes

Loading…
Cancel
Save