Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Menambahkan fungsi untuk tambah dan sunting pembelian barang #74

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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