Skip to content

Commit

Permalink
Menambahkan fungsi untuk tambah dan sunting pembelian barang (#74)
Browse files Browse the repository at this point in the history
* Restruktur data dan menambahkan tipe `ProductMovement`

* Menyempurnakan komponen `<ConfirmationModal />`

- Menambah warna `default`
- Menyembunyikan tombol tutup (`hideCloseButton`)
- Menonaktifkan `isDismissable`

* Menambahkan variabel env `NEXT_PUBLIC_APP_NAME`

* Mendaftarkan file `.env` pada `.gitignore`

* Menambahkan navigasi ke halaman pembelian

* Menambahakan halaman daftar pembelian

* Menambahkan fungsi untuk tambah dan sunting pembelian barang
  • Loading branch information
sensasi-delight authored Sep 14, 2024
1 parent 4bf8d31 commit 9684d7a
Show file tree
Hide file tree
Showing 28 changed files with 496 additions and 55 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
NEXT_PUBLIC_APP_NAME="Sensasi POS"
SENTRY_AUTH_TOKEN=
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

.env
4 changes: 1 addition & 3 deletions src/@types/base-64-blob.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
type Base64Blob = string

export default Base64Blob
export type Base64Blob = string
1 change: 1 addition & 0 deletions src/@types/iso-date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ISODate = string
21 changes: 19 additions & 2 deletions src/app/(authenticated user)/@navbar/default.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ import {
NavbarItem,
Link,
Button,
Tooltip,
} from '@nextui-org/react'
import NextLink from 'next/link'
import { ComputerIcon, FileSpreadsheetIcon } from 'lucide-react'
import {
ComputerIcon,
FileSpreadsheetIcon,
ShoppingCartIcon,
} from 'lucide-react'
// components
import ThemeSwitcher from '@/components/theme-switcher'
// sub-components
import SettingsDropdownButtonInNavbar from './settings-dropdown-button'
import PageUrlEnum from '@/enums/page-url'

/**
*
Expand Down Expand Up @@ -55,7 +61,18 @@ export default function NavbarSlot() {
</Button>
</NavbarItem>

<NavbarItem>
<NavbarItem className="flex gap-2">
<Tooltip content="Pengadaan" color="primary" showArrow size="lg">
<Button
isIconOnly
href={PageUrlEnum.PURCHASE_LIST}
as={NextLink}
variant="light"
color="primary">
<ShoppingCartIcon />
</Button>
</Tooltip>

<SettingsDropdownButtonInNavbar />
</NavbarItem>
</NavbarContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function SettingsDropdownButtonInNavbar() {
return (
<Dropdown>
<DropdownTrigger>
<Button color="primary" variant="light" isIconOnly size="sm">
<Button color="primary" variant="light" isIconOnly>
<SettingsIcon />
</Button>
</DropdownTrigger>
Expand Down
2 changes: 1 addition & 1 deletion src/app/(authenticated user)/data/products/[id]/_hook.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// types
import type Product from '@/models/table-types/product'
import type { Product } from '@/models/table-types/product'
import type { ProductFormProps } from '../_components/product-form/_props'
// vendors
import { useRouter } from 'next/navigation'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import dayjs from 'dayjs'
import { useState } from 'react'
// components
import ConfirmationModal from '@/components/confirmation-modal'
import { ConfirmationModal } from '@/components/confirmation-modal'
// models
import db from '@/models/db'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

// types
import type Product from '@/models/table-types/product'
import type { Product } from '@/models/table-types/product'
// vendors
import {
Card,
Expand All @@ -23,16 +23,7 @@ import PageUrlEnum from '@/enums/page-url'
import { useHook } from './_hook'

export function ProductCard({
data: {
id,
name,
base_cost,
default_price,
qty_unit,
qty,
category,
image_file,
},
data: { id, name, default_price, qty_unit, category, image_file, stocks },
className,
as,
}: {
Expand All @@ -43,6 +34,12 @@ export function ProductCard({
}) {
const { handleOpenDeleteModal, deleteConfirmationModal } = useHook(id)

const totalStock = stocks.reduce((acc, { qty }) => acc + qty, 0)
const cost =
totalStock === 0
? 0
: stocks.reduce((acc, { qty, cost }) => acc + cost * qty, 0) / totalStock

return (
<Card
as={as}
Expand Down Expand Up @@ -76,14 +73,15 @@ export function ProductCard({
<Chip size="sm">{category ?? 'Tanpa Kategori'}</Chip>
<span></span>
<span>
{formatNumber(qty)} {qty_unit}
{formatNumber(totalStock)}
{qty_unit}
</span>
</div>
</div>

<div className="flex flex-col justify-center col-span-3 md:col-span-2">
<h3 className="text-sm">HPP</h3>
<p className="text-xl">{formatNumber(base_cost)}</p>
<p className="text-xl">{formatNumber(cost)}</p>
</div>

<div className="flex flex-col justify-center items-center col-span-3 md:col-span-2">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type Product from '@/models/table-types/product'
import type { Product } from '@/models/table-types/product'
import { type FormEvent, useState } from 'react'
import type { ProductFormProps } from './_props'
import { useDebouncedCallback } from 'use-debounce'
Expand Down Expand Up @@ -53,14 +53,6 @@ export function useHook(
formValues.code = formValues.id.toString()
}

if (!formValues.base_cost) {
formValues.base_cost = 0
}

if (!formValues.qty) {
formValues.qty = 0
}

onSubmit?.(formValues)
},
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type Product from '@/models/table-types/product'
import type { Product } from '@/models/table-types/product'

export interface ProductFormProps {
id: HTMLFormElement['id']
Expand Down
92 changes: 92 additions & 0 deletions src/app/(authenticated user)/purchases/(form)/[id]/page-hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use client'

import type { ProductMovement } from '@/models/table-types/product-movement'
import type { FormValues } from '../_types/form-values'

import db from '@/models/db'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { updateStocksOnReceived } from '../_functions/update-stocks-on-received'
import { validateFormValues } from '../_functions/validate-form-values'

export function usePageHook(idParam: string) {
const router = useRouter()

const [formValues, setFormValues] = useState<FormValues>()

useEffect(() => {
if (isNaN(parseInt(idParam))) {
throw new Error('Invalid ID')
}

getData(parseInt(idParam))
.then(setFormValues)
.catch(error => {
throw error
})

return () => {
setFormValues(undefined)
}
}, [idParam])

return {
formValues,

handleCancel: () => router.back(),

handleSubmit: () => {
if (!formValues) return

const validated = validateFormValues(formValues)

db.productMovements
.update(validated.id, {
...validated,
updated_at: new Date().toISOString(),
})
.then(() => {
if (validated.type !== 'purchase') {
throw new Error('Invalid type')
}

if (validated.additional_info.received_at) {
updateStocksOnReceived(validated)
}
})
.then(() => router.back())
.catch(error => {
throw error
})
},
}
}

async function getData(id: number) {
return new Promise<ProductMovement>((resolve, reject) => {
db.productMovements
.get(id)
.then(data => {
if (!data) {
reject(new Error('not found'))
return
}

if (data.type !== 'purchase') {
reject(new Error('not found'))
return
}

if (data.additional_info.received_at ?? data.additional_info.paid_at) {
reject(
new Error(
'Tidak dapat mengubah data yang sudah diterima atau dibayar',
),
)
}

resolve(data)
})
.catch(reject)
})
}
44 changes: 44 additions & 0 deletions src/app/(authenticated user)/purchases/(form)/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client'

import {
Button,
Card,
CardBody,
CardFooter,
CardHeader,
} from '@nextui-org/react'
import { usePageHook } from './page-hook'

const FORM_ID = 'purchase-form'

export default function PurchaseFormPage({
params: { id },
}: {
params: { id: string }
}) {
const { handleSubmit, handleCancel, formValues } = usePageHook(id)

return (
<Card>
<CardHeader>Sunting Pembelian — NO. {formValues?.id}</CardHeader>

<CardBody>
<form
id={FORM_ID}
onSubmit={() => {
handleSubmit()
}}></form>
</CardBody>

<CardFooter>
<Button variant="light" color="primary" onClick={handleCancel}>
Batal
</Button>

<Button color="primary" form={FORM_ID} type="submit">
Simpan
</Button>
</CardFooter>
</Card>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import db from '@/models/db'
import { ProductMovement } from '@/models/table-types/product-movement'

export function updateStocksOnReceived(productMovement: ProductMovement) {
productMovement.items.forEach(item => {
db.products
.update(item.product_state.id, product => {
const inQty = item.qty
const inWorth = item.qty * item.price

const existsStock = product.stocks.find(
stock => stock.warehouse_id === productMovement.warehouse_state.id,
)

const existsQty = existsStock?.qty ?? 0
const existsWorth = existsStock?.cost ?? 0

const newQty = existsQty + inQty
const newWorth = existsWorth + inWorth
const newCost = newWorth / newQty

if (existsStock) {
existsStock.qty = newQty
existsStock.cost = newCost
} else {
product.stocks.push({
warehouse_id: productMovement.warehouse_state.id,
qty: newQty,
cost: newCost,
})
}
})
.catch(error => {
throw error
})
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { ProductMovement } from '@/models/table-types/product-movement'
import type { FormValues } from '../_types/form-values'

export function validateFormValues(formValues: FormValues) {
return formValues as ProductMovement
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { ProductMovement } from '@/models/table-types/product-movement'

export type FormValues = Partial<ProductMovement>
Loading

0 comments on commit 9684d7a

Please sign in to comment.