You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
458 lines
18 KiB
JavaScript
458 lines
18 KiB
JavaScript
import React, { useState, useEffect } from 'react'
|
|
import NumberFormat from 'react-number-format'
|
|
import moment from 'moment'
|
|
import { Head, useForm } from '@inertiajs/inertia-react'
|
|
import { Inertia } from '@inertiajs/inertia'
|
|
import { toast } from 'react-toastify'
|
|
import { formatIDR } from '@/utils'
|
|
import { usePrevious } from 'react-use'
|
|
import Pagination from '@/Components/Pagination'
|
|
import Authenticated from '@/Layouts/Authenticated'
|
|
import SelectInput from '@/Components/CategorySelectInput'
|
|
|
|
export default function Transaction(props) {
|
|
const { transactions, _search, _startDate, _endDate } = props
|
|
const [startDate, setStartDate] = useState( _startDate ? _startDate :
|
|
moment().startOf('month').format('YYYY-MM-DD')
|
|
)
|
|
const [endDate, setEndDate] = useState( _endDate ? _endDate :
|
|
moment().endOf('month').format('YYYY-MM-DD')
|
|
)
|
|
const [search, setSearch] = useState(_search ? _search : '')
|
|
const prevValues = usePrevious({search, startDate, endDate})
|
|
|
|
const [transaction, setTransaction] = useState(null)
|
|
const [showForm, setShowForm] = useState(false)
|
|
const {
|
|
data,
|
|
setData,
|
|
errors,
|
|
post,
|
|
put,
|
|
processing,
|
|
delete: destroy,
|
|
} = useForm({
|
|
category_name: '',
|
|
category_id: '',
|
|
description: '',
|
|
amount: 0,
|
|
date: moment().format('yyyy-MM-DD'),
|
|
is_income: 0,
|
|
income_type: 0,
|
|
})
|
|
|
|
const toggleForm = (transaction = null) => {
|
|
setShowForm(!showForm)
|
|
if (transaction !== null) {
|
|
handleEdit(transaction)
|
|
} else {
|
|
handleReset()
|
|
}
|
|
}
|
|
|
|
const toggleCashType = () => {
|
|
setData('income_type', data.income_type === 0 ? 1 : 0)
|
|
}
|
|
|
|
const toggleIncome = () => {
|
|
setData({
|
|
...data,
|
|
category_id: '',
|
|
description: '',
|
|
is_income: data.is_income === 0 ? 1 : 0,
|
|
income_type: 1,
|
|
})
|
|
}
|
|
|
|
const handleSelectedcategory = (category) => {
|
|
setData({
|
|
...data,
|
|
description: category.description,
|
|
category_id: category.id,
|
|
category_name: category.name,
|
|
})
|
|
}
|
|
|
|
const handleChange = (e) => {
|
|
const key = e.target.id
|
|
const value = e.target.value
|
|
setData(key, value)
|
|
}
|
|
|
|
const handleReset = () => {
|
|
setTransaction(null)
|
|
setSearch('')
|
|
setStartDate(moment().startOf('month').format('YYYY-MM-DD'))
|
|
setEndDate(moment().endOf('month').format('YYYY-MM-DD'))
|
|
setData({
|
|
category_id: '',
|
|
description: '',
|
|
amount: 0,
|
|
date: moment().format('yyyy-MM-DD'),
|
|
is_income: 0,
|
|
income_type: 0,
|
|
})
|
|
}
|
|
|
|
const handleDelete = (transaction) => {
|
|
destroy(route('transactions.destroy', transaction), {
|
|
onBefore: () =>
|
|
confirm('Are you sure you want to delete this record?'),
|
|
onSuccess: () =>
|
|
Promise.all([
|
|
handleReset(),
|
|
toast.success('data has been deleted'),
|
|
]),
|
|
})
|
|
}
|
|
|
|
const handleEdit = (transaction) => {
|
|
setTransaction(transaction)
|
|
setData({
|
|
category_name: transaction.category?.name,
|
|
category_id:
|
|
transaction.category_id === null ? '' : transaction.category_id,
|
|
description: transaction.description,
|
|
amount: transaction.amount,
|
|
date: moment(transaction.date).format('yyyy-MM-DD'),
|
|
is_income: +transaction.is_income,
|
|
income_type: +transaction.income_type,
|
|
})
|
|
}
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault()
|
|
if (transaction !== null) {
|
|
put(route('transactions.update', transaction), {
|
|
onSuccess: () =>
|
|
Promise.all([
|
|
toggleForm(),
|
|
handleReset(),
|
|
toast.success('The Data has been changed'),
|
|
]),
|
|
})
|
|
return
|
|
}
|
|
post(route('transactions.store'), {
|
|
onSuccess: () =>
|
|
Promise.all([
|
|
toggleForm(),
|
|
handleReset(),
|
|
toast.success('Data has been saved'),
|
|
]),
|
|
})
|
|
}
|
|
|
|
useEffect(() => {
|
|
if(prevValues) {
|
|
Inertia.get(route(route().current()), { q: search, startDate, endDate }, {
|
|
replace: true,
|
|
preserveState: true
|
|
})
|
|
}
|
|
}, [search, startDate, endDate])
|
|
|
|
return (
|
|
<Authenticated
|
|
errors={props.errors}
|
|
header={
|
|
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
|
|
Transaction
|
|
</h2>
|
|
}
|
|
>
|
|
<Head title="Transaction" />
|
|
|
|
<div className="flex flex-col space-y-2 py-12">
|
|
<div className="w-full px-6 md:pr-8">
|
|
<div className="card bg-white">
|
|
<div className="card-body">
|
|
<div
|
|
className="btn btn-secondary max-w-min my-2"
|
|
onClick={() => toggleForm()}
|
|
>
|
|
Add
|
|
</div>
|
|
<div className="w-full flex flex-col md:flex-row justify-between my-2 space-x-0 md:space-x-2 space-y-2 md:space-y-0">
|
|
<div className="form-control w-full">
|
|
<input
|
|
type="date"
|
|
className="input input-bordered"
|
|
value={startDate}
|
|
onChange={(e) =>
|
|
setStartDate(e.target.value)
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="form-control w-full">
|
|
<input
|
|
type="date"
|
|
className="input input-bordered"
|
|
value={endDate}
|
|
onChange={(e) =>
|
|
setEndDate(e.target.value)
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="form-control w-full">
|
|
<input
|
|
type="text"
|
|
className="input input-bordered"
|
|
value={search}
|
|
onChange={(e) =>
|
|
setSearch(e.target.value)
|
|
}
|
|
placeholder="Search"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="overflow-x-auto">
|
|
<table className="table w-full table-zebra">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Type</th>
|
|
<th>Category Name</th>
|
|
<th className="w-32">
|
|
Cash In/Out
|
|
</th>
|
|
<th>Description</th>
|
|
<th>Amount</th>
|
|
<th className="w-52"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody
|
|
className={
|
|
processing ? 'opacity-70' : ''
|
|
}
|
|
>
|
|
{transactions?.data?.map(
|
|
(transaction) => (
|
|
<tr key={transaction.id}>
|
|
<td>
|
|
{moment(
|
|
transaction.date
|
|
).format('DD/MM/yyyy')}
|
|
</td>
|
|
<td>
|
|
{+transaction.is_income ===
|
|
0 ? (
|
|
<div className="badge badge-secondary">
|
|
Expense
|
|
</div>
|
|
) : (
|
|
<div className="badge badge-primary">
|
|
Income
|
|
</div>
|
|
)}
|
|
</td>
|
|
<td>
|
|
{
|
|
transaction
|
|
?.category?.name
|
|
}
|
|
</td>
|
|
<td>
|
|
{+transaction.income_type ===
|
|
0 ? (
|
|
<div className="badge badge-secondary">
|
|
Cash Out
|
|
</div>
|
|
) : (
|
|
<div className="badge badge-accent">
|
|
Cash In
|
|
</div>
|
|
)}
|
|
</td>
|
|
<td>
|
|
{
|
|
transaction.description
|
|
}
|
|
</td>
|
|
<td>
|
|
{formatIDR(
|
|
transaction.amount
|
|
)}
|
|
</td>
|
|
<td>
|
|
<div
|
|
className="btn btn-warning mx-1"
|
|
onClick={() =>
|
|
toggleForm(
|
|
transaction
|
|
)
|
|
}
|
|
>
|
|
Edit
|
|
</div>
|
|
<div
|
|
className="btn btn-error mx-1"
|
|
onClick={() =>
|
|
handleDelete(
|
|
transaction
|
|
)
|
|
}
|
|
>
|
|
Delete
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
)
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<Pagination links={transactions?.links} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
id="create-modal"
|
|
className="modal"
|
|
style={
|
|
showForm
|
|
? {
|
|
opacity: 1,
|
|
pointerEvents: 'auto',
|
|
visibility: 'visible',
|
|
}
|
|
: {}
|
|
}
|
|
>
|
|
<div className="modal-box">
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Date</span>
|
|
</label>
|
|
<input
|
|
type="date"
|
|
className={`input input-bordered ${
|
|
errors.date ? 'input-error' : ''
|
|
}`}
|
|
id="date"
|
|
value={data.date}
|
|
onChange={handleChange}
|
|
/>
|
|
<label className="label">
|
|
<span className="label-text-alt">
|
|
{errors.date}
|
|
</span>
|
|
</label>
|
|
</div>
|
|
<div className="form-control">
|
|
<label className="cursor-pointer label">
|
|
<input
|
|
type="checkbox"
|
|
checked={data.is_income === 1 ? true : false}
|
|
onChange={toggleIncome}
|
|
className="checkbox checkbox-primary"
|
|
/>
|
|
<span className="label-text font-bold">Income</span>
|
|
</label>
|
|
</div>
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Category</span>
|
|
</label>
|
|
<SelectInput
|
|
value={data.category_name}
|
|
onItemSelected={handleSelectedcategory}
|
|
disabled={data.is_income === 1}
|
|
invalid={errors.category_id ? true : false}
|
|
/>
|
|
<label className="label">
|
|
<span className="label-text-alt">
|
|
{errors.category_id}
|
|
</span>
|
|
</label>
|
|
</div>
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Description</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
placeholder="Description"
|
|
className={`input input-bordered ${
|
|
errors.description ? 'input-error' : ''
|
|
}`}
|
|
id="description"
|
|
value={data.description}
|
|
onChange={handleChange}
|
|
/>
|
|
<label className="label">
|
|
<span className="label-text-alt">
|
|
{errors.description}
|
|
</span>
|
|
</label>
|
|
</div>
|
|
<div className="form-control">
|
|
<label className="cursor-pointer label">
|
|
<span className="label-text font-bold">
|
|
{data.income_type === 0
|
|
? 'Cash Out'
|
|
: 'Cash In'}
|
|
</span>
|
|
<input
|
|
type="checkbox"
|
|
checked={data.income_type === 0 ? true : false}
|
|
disabled={data.is_income === 1}
|
|
className="toggle"
|
|
onChange={toggleCashType}
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Amount</span>
|
|
</label>
|
|
<NumberFormat
|
|
thousandSeparator={true}
|
|
className={`input input-bordered ${
|
|
errors.amount ? 'input-error' : ''
|
|
}`}
|
|
value={data.amount}
|
|
thousandSeparator="."
|
|
decimalSeparator=","
|
|
onValueChange={({ value }) =>
|
|
setData('amount', value)
|
|
}
|
|
/>
|
|
<label className="label">
|
|
<span className="label-text-alt">
|
|
{errors.amount}
|
|
</span>
|
|
</label>
|
|
</div>
|
|
<div className="modal-action">
|
|
<button
|
|
className={`btn btn-primary ${
|
|
processing && 'animate-spin'
|
|
}`}
|
|
onClick={handleSubmit}
|
|
disabled={processing}
|
|
>
|
|
Add
|
|
</button>
|
|
<button
|
|
className="btn btn-secondary"
|
|
onClick={handleReset}
|
|
disabled={processing}
|
|
>
|
|
Clear
|
|
</button>
|
|
<button
|
|
className="btn btn-outline btn-secondary"
|
|
onClick={() => toggleForm()}
|
|
disabled={processing}
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Authenticated>
|
|
)
|
|
}
|