products done, selection input hook

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

106
package-lock.json generated

@ -26,6 +26,7 @@
"axios": "^0.21.1",
"framer-motion": "^4.1.17",
"is_js": "^0.9.0",
"query-string": "^7.0.1",
"react": "^17.0.2",
"react-datepicker": "^4.2.0",
"react-dom": "^17.0.2",
@ -10264,6 +10265,14 @@
"node": ">=8"
}
},
"node_modules/filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@ -15310,6 +15319,26 @@
"node": ">=4"
}
},
"node_modules/normalize-url/node_modules/query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"dependencies": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/normalize-url/node_modules/strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@ -17609,15 +17638,20 @@
}
},
"node_modules/query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz",
"integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==",
"dependencies": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
"decode-uri-component": "^0.2.0",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/querystring": {
@ -20033,6 +20067,14 @@
"node": ">= 6"
}
},
"node_modules/split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
"engines": {
"node": ">=6"
}
},
"node_modules/split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@ -20221,11 +20263,11 @@
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
},
"node_modules/strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=",
"engines": {
"node": ">=0.10.0"
"node": ">=4"
}
},
"node_modules/string_decoder": {
@ -31187,6 +31229,11 @@
"to-regex-range": "^5.0.1"
}
},
"filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs="
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@ -35013,6 +35060,22 @@
"prepend-http": "^1.0.0",
"query-string": "^4.1.0",
"sort-keys": "^1.0.0"
},
"dependencies": {
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"requires": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
}
},
"strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
}
}
},
"npm-run-path": {
@ -36841,12 +36904,14 @@
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz",
"integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==",
"requires": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
"decode-uri-component": "^0.2.0",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
}
},
"querystring": {
@ -38765,6 +38830,11 @@
}
}
},
"split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
},
"split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@ -38922,9 +38992,9 @@
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
},
"strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
},
"string_decoder": {
"version": "1.3.0",

@ -22,6 +22,7 @@
"axios": "^0.21.1",
"framer-motion": "^4.1.17",
"is_js": "^0.9.0",
"query-string": "^7.0.1",
"react": "^17.0.2",
"react-datepicker": "^4.2.0",
"react-dom": "^17.0.2",

@ -1,8 +1,9 @@
import useSWR from "swr"
import qs from "query-string"
export function useProducts(user, { page = 1, q = '' }) {
export function useProducts(user, params) {
const { data, error } = useSWR([
`/products?page=${page}&q=${q}`, user.accessToken
`/products?${qs.stringify(params)}`, user.accessToken
])
return [
@ -11,9 +12,9 @@ export function useProducts(user, { page = 1, q = '' }) {
]
}
export function useCategories(user, { page = 1, q }) {
export function useCategories(user, params) {
const { data, error } = useSWR([
`/categories?page=${page}&q=${q}`, user.accessToken
`/categories?${qs.stringify(params)}`, user.accessToken
])
return [

@ -2,7 +2,7 @@ import { Box } from "@chakra-ui/react"
export default function Card(props) {
return (
<Box bg="white" shadow="lg" minW="15rem" minH="1" rounded="md" p="3.5" mx="0.5rem">
<Box bg="white" shadow="lg" minW={{ base: "10rem", lg: "15rem"}} minH="1" rounded="md" p="3.5" mx="0.5rem">
{props.children}
</Box>
)

@ -9,9 +9,9 @@ export function useModalState(init = false) {
setVal(data)
}
return {
return [
isOpen,
toggle,
selected: val
}
val
]
}

@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
import { FormControl, FormLabel, Input, Box, InputGroup, InputRightElement } from "@chakra-ui/react";
import { FormControl, FormLabel, Input, Box, InputGroup, InputRightElement, InputRightAddon } 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">
<Box px="3" mt="3">
<InputGroup>
<Input
value={search}
@ -52,6 +52,38 @@ export function FormInput(props) {
)
}
export function FormInputSelection(props) {
const { data: [name, val], onRemove, onFormClick } = props
return (
<Box>
<FormControl id={name} mb="3">
<FormLabel fontWeight="bold">{name}</FormLabel>
<InputGroup>
<Input
focusBorderColor="red.500"
type={"text"}
value={val}
readOnly={true}
{...props}
bg={"gray.100"}
onClick={onFormClick}
/>
{onRemove && val ?
(
<InputRightAddon
children={<FontAwesomeIcon icon="times"/>}
onClick={onRemove}
{...props}
/>
) :
( <InputRightAddon children={<FontAwesomeIcon icon="ellipsis-v" />} {...props}/> )
}
</InputGroup>
</FormControl>
</Box>
)
}
export function FormInputNumber(props) {
const { data:[name, val, setVal] } = props
return (

@ -16,9 +16,10 @@ export function usePaginate() {}
)
}
export function Pagination({ page = 1, setPage, totalPages = 1}) {
export function Pagination(props) {
const { page = 1, setPage, totalPages = 1, ...restProps} = props
return (
<HStack spacing="2" mt="3" justifyContent="flex-end">
<HStack spacing="2" mt="3" justifyContent="flex-end" {...restProps}>
<StyledBox
bg={page === 1 ? "red.200" : "red.500"}
onClick={() => setPage(+page - 1 <= 0 ? 1 : +page - 1)}

@ -18,6 +18,7 @@ import {
import { Link } from "react-router-dom"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { mutate } from 'swr'
import qs from "query-string"
import {
Breadcrumb,
Card,
@ -38,11 +39,11 @@ export default function List() {
const [page, setPage] = useState(1)
const [search, setSearch] = useState('')
const q = useDebounce(search, 300)
const q = useDebounce(search, 600)
const params = { page, q }
const [data, error] = useCategories(user, params)
const [data, error] = useCategories(user, { page, q })
const {isOpen, toggle, selected} = useModalState(false)
const [isOpen, toggle, selected] = useModalState(false)
const handleDelete = async () => {
await deleteCategory(selected.id, user.accessToken)
@ -55,7 +56,7 @@ export default function List() {
duration: 4000,
isClosable: true
})
mutate([`/categories?page=${page}&q=${q}`, user.accessToken])
mutate([`/categories?${qs.stringify(params)}`, user.accessToken])
})
.catch((err) => {
toast({
@ -82,7 +83,7 @@ export default function List() {
<>
<Breadcrumb main={["/categories", "kategori"]}/>
<Card>
<Button as={Link} to="/categories/create" size="md">
<Button as={Link} to="/categories/create" size="md" mb="3">
tambah
</Button>
<SearchInput setter={[search, setSearch]}/>

@ -0,0 +1,72 @@
import { useState } from "react"
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalCloseButton,
Table,
Thead,
Tr,Th,Td,
Tbody,
Alert,
AlertIcon
} from "@chakra-ui/react"
import { Loading, Pagination, SearchInput, useDebounce } from "../../components/Common"
import { useAuth } from "../../context/AppContext"
import { useCategories } from "../../api"
export default function ModalCom(props) {
const { isOpen, toggle, onClose } = props
const { user } = useAuth()
const [page, setPage] = useState(1)
const [search, setSearch] = useState('')
const q = useDebounce(search, 600)
const [data, error] = useCategories(user, { page, q })
return (
<Modal isOpen={isOpen} onClose={toggle}>
<ModalOverlay />
<ModalContent minW="40rem">
<ModalHeader>kategori</ModalHeader>
<ModalCloseButton />
<ModalBody>
<SearchInput setter={[search, setSearch]}/>
{error && (
<Alert status="error">
<AlertIcon />
{error.message}
</Alert>
)}
{data ? (
<>
<Table variant="simple" mt="2" mb="4">
<Thead>
<Tr>
<Th>nama</Th>
</Tr>
</Thead>
<Tbody>
{data.categories.map((category) => (
<Tr key={category.id} onClick={() => {
onClose(category)
toggle()
}} _hover={{bg: "gray.200"}}>
<Td>{ category.name }</Td>
</Tr>
))}
</Tbody>
</Table>
<Pagination page={page} setPage={setPage} totalPages={data.meta.totalPages} mb="4"/>
</>
) : (
<Loading/>
)}
</ModalBody>
</ModalContent>
</Modal>
)
}

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

@ -1,29 +1,97 @@
import {
Button,
} from "@chakra-ui/react"
import { Alert, AlertIcon, Button, useToast } from "@chakra-ui/react"
import { useState } from "react"
import { Breadcrumb, Card, FormInput, FormInputNumber } from "../../components/Common"
import {
Breadcrumb,
Card,
FormInput,
FormInputNumber,
FormInputSelection,
useModalState
} from "../../components/Common"
import { createProduct } from "./Api"
import CategoryModalSelection from "../categories/Modal"
import { useAuth } from "../../context/AppContext"
export default function Create(props) {
const { user } = useAuth()
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({})
const [category, setCategory] = useState({name: ''})
const toast = useToast()
const [submit, setSubmit] = useState(false)
const [error, setError] = useState(null)
const [ isOpen, toggle ] = useModalState()
const handleSubmit = () => {
setSubmit(true)
createProduct({
name,
description,
cost,
price,
stock,
category_id: category ? category.id : ''
}, user.accessToken)
.then(res => {
toast({
title: res.status,
description: "item ditambahkan",
status: "success",
duration: 4000,
isClosable: true,
position: "top-right"
})
props.history.push('/products')
})
.catch((err) => {
setError(err.message)
})
.finally(() => {
setSubmit(false)
})
}
return (
<>
<Breadcrumb main={["/products", "produk", "baru"]}/>
<Card>
{error !== null && (
<Alert status="error" mb="5" rounded="md">
<AlertIcon />
{error}
</Alert>
)}
<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>
<FormInputSelection
data={['kategori', category.name]}
readOnly={true}
onFormClick={() => toggle()}
/>
<Button
mt="4"
isLoading={submit}
onClick={handleSubmit}
>
simpan
</Button>
</Card>
<CategoryModalSelection
isOpen={isOpen}
toggle={toggle}
onClose={(category) => {
setCategory(category)
}}
/>
</>
)
}

@ -1,7 +0,0 @@
export default function Detail(props) {
const id = props.match.params.id
return (
<div>Product: { id }</div>
)
}

@ -0,0 +1,126 @@
import { Alert, AlertIcon, Button, useToast } from "@chakra-ui/react"
import { useState, useEffect } from "react"
import {
Breadcrumb,
Card,
FormInput,
FormInputNumber,
FormInputSelection,
Loading,
useModalState
} from "../../components/Common"
import { getProduct, updateProduct } from "./Api"
import CategoryModalSelection from "../categories/Modal"
import { useAuth } from "../../context/AppContext"
import { formatIDR } from "../../utils"
export default function Edit(props) {
const id = props.match.params.id
const { user } = useAuth()
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({name: ''})
const toast = useToast()
const [loading, setLoading] = useState(false)
const [submit, setSubmit] = useState(false)
const [error, setError] = useState(null)
const [isOpen, toggle] = useModalState()
const handleSubmit = () => {
setSubmit(true)
updateProduct(id, {
name,
description,
cost,
price,
stock,
category_id: category ? category.id : ''
}, user.accessToken)
.then(res => {
toast({
title: res.status,
description: "item diubah",
status: "success",
duration: 4000,
isClosable: true,
position: "top-right"
})
props.history.push('/products')
})
.catch((err) => {
setError(err.message)
})
.finally(() => {
setSubmit(false)
})
}
useEffect(() => {
setLoading(true)
getProduct(id, user.accessToken)
.then(res => {
setName(res.product.name)
setDescription(res.product.description)
setPrice(res.product.price)
setCost(res.product.cost)
setStock(res.product.stock)
setCategory({
id: res.product.category_id,
name: res.product.category_name
})
})
.catch(err => setError(err))
.finally(() => setLoading(false))
return () => {}
}, [id, user])
return (
<>
<Breadcrumb main={["/products", "produk", "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]}/>
<FormInputNumber data={['harga beli', formatIDR(cost), setCost]}/>
<FormInputNumber data={['harga jual', formatIDR(price), setPrice]}/>
<FormInputNumber data={['stok', formatIDR(stock), setStock]}/>
<FormInputSelection
data={['kategori', category.name]}
readOnly={true}
onFormClick={() => toggle()}
/>
<Button
mt="4"
isLoading={submit}
onClick={handleSubmit}
>
simpan
</Button>
</>
)}
</Card>
<CategoryModalSelection
isOpen={isOpen}
toggle={toggle}
onClose={(category) => {
setCategory(category)
}}
/>
</>
)
}

@ -9,29 +9,72 @@ import {
Td,
Th,
Tbody,
Menu,
MenuButton,
MenuList,
MenuItem,
useToast,
Grid,
} from "@chakra-ui/react"
import { useProducts } from "../../api"
import { mutate } from "swr"
import qs from "query-string"
import {
Breadcrumb,
Card,
Loading,
SearchInput,
Pagination,
useDebounce
AlertDialog,
useDebounce,
useModalState,
FormInputSelection
} from "../../components/Common"
import { deleteProduct } from "./Api"
import { formatIDR } from "../../utils"
import { Link } from "react-router-dom"
import { useAuth } from "../../context/AppContext"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import CategorySelectionModal from "../categories/Modal"
export default function List({ history }) {
const { user } = useAuth()
const toast = useToast()
const [page, setPage] = useState(1)
const [search, setSearch] = useState('')
const q = useDebounce(search, 600)
const [ data, error ] = useProducts(user, { page, q })
const [category, setCategory] = useState({name: ''})
const params = { page, q, withCategory: true, withStock: true, categoryId: category?.id }
const [ data, error ] = useProducts(user, params)
const [isOpen, toggle, selected] = useModalState()
const [isCategoryOpen, toggleCategory] = useModalState()
const handleItemClick = (id) => {
history.push(`/products/${id}`)
const handleDelete = () => {
deleteProduct(selected.id, user.accessToken)
.then(res => {
toast({
title: res.status,
description: "item dihapus",
status: "success",
position: "top-right",
duration: 4000,
isClosable: true
})
mutate([`/products?${qs.stringify(params)}`, user.accessToken])
})
.catch(err => {
toast({
title: err.status,
description: err.message,
status: "error",
position: "top-right",
duration: 4000,
isClosable: true
})
})
}
if(error) {
@ -47,28 +90,61 @@ export default function List({ history }) {
<>
<Breadcrumb main={["/products", "produk"]}/>
<Card>
<Button as={Link} to="/products/create" size="md">
<Button as={Link} to="/products/create" size="md" mb="3">
tambah
</Button>
<SearchInput setter={[search, setSearch]}/>
<Grid gap="1" templateColumns="repeat(2, 1fr)">
<SearchInput setter={[search, setSearch]}/>
<FormInputSelection
mt="1"
data={["", category.name]}
placeholder={"kategori"}
onFormClick={toggleCategory}
onRemove={() => {
setCategory({name: ''})
}}
/>
</Grid>
{data ? (
<>
<Table variant="simple" mt="2" mb="4">
<Thead>
<Tr>
<Th>nama</Th>
<Th>kategori</Th>
<Th isNumeric>harga beli</Th>
<Th isNumeric>harga jual</Th>
<Th isNumeric>stok</Th>
<Th>deskripsi</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{data.products.map((product) => (
<Tr onClick={() => handleItemClick(product.id)} key={product.id}>
<Tr key={product.id}>
<Td>{ product.name }</Td>
<Td>{ product.category_name }</Td>
<Td isNumeric>{ formatIDR(product.cost) }</Td>
<Td isNumeric>{ formatIDR(product.price) }</Td>
<Td isNumeric>{ formatIDR(product.stock) }</Td>
<Td>{ product.description }</Td>
<Td isNumeric>
<Menu>
<MenuButton as={Button}>
<FontAwesomeIcon icon="ellipsis-v"/>
</MenuButton>
<MenuList>
<MenuItem as={Link} to={`/products/${product.id}/edit`}>ubah</MenuItem>
<MenuItem
onClick={() => {
toggle(product)
}}
>
hapus
</MenuItem>
</MenuList>
</Menu>
</Td>
</Tr>
))}
</Tbody>
@ -79,6 +155,18 @@ export default function List({ history }) {
<Loading/>
)}
</Card>
<AlertDialog
isOpen={isOpen}
onClose={handleDelete}
toggle={() => toggle()}
/>
<CategorySelectionModal
isOpen={isCategoryOpen}
onClose={(category) => {
setCategory(category)
}}
toggle={toggleCategory}
/>
</>
)
}

@ -2,14 +2,14 @@ import { Switch, Route } from 'react-router-dom'
import Create from './Create'
import List from './List'
import Detail from './Detail'
import Edit from './Edit'
function routes(props) {
return (
<Switch>
<Route path="/products/:id/edit" exact component={Edit} />
<Route path="/products/create" exact component={Create} />
<Route path="/products" exact component={List} />
<Route path="/products/:id" exact component={Detail} />
</Switch>
)
}

Loading…
Cancel
Save