filter sale

dev
Aji Kamaludin 1 year ago
parent 5cf19db71d
commit 9cd209dd33
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -3,16 +3,40 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Location;
use App\Models\Sale;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class SaleController extends Controller
{
public function index(Request $request)
{
$customer_with_deposits = Sale::with('customer')
->whereHas('customer', function ($query) {
$query->whereHas('locationFavorites', function ($query) {
$query->whereIn('id', Location::all()->pluck('id')->toArray());
});
})
->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])
->where('payed_with', Sale::PAYED_WITH_DEPOSIT)
->groupBy('customer_id')
->selectRaw('SUM(amount) as total, customer_id')
->orderBy('total', 'desc')
->limit(10)
->get();
$customer_with_paylaters = Sale::with('customer')
->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])
->where('payed_with', Sale::PAYED_WITH_PAYLATER)
->groupBy('customer_id')
->selectRaw('SUM(amount) as total, customer_id')
->orderBy('total', 'desc')
->limit(10)
->get();
$query = Sale::with(['items', 'customer.level'])
->withCount(['items'])
->orderBy('updated_at', 'desc');
->withCount(['items']);
if ($request->q != '') {
$query->where('code', 'ilike', "%$request->q%")
@ -23,8 +47,40 @@ class SaleController extends Controller
});
}
if ($request->location_ids != '') {
$query->whereHas('customer', function ($q) use ($request) {
$q->whereHas('locationFavorites', function ($q) use ($request) {
$q->whereIn('location_id', $request->location_ids);
});
});
}
if ($request->payment != '') {
$query->where('payed_with', $request->payment);
} else {
$query->whereIn('payed_with', [Sale::PAYED_WITH_PAYLATER, Sale::PAYED_WITH_DEPOSIT]);
}
if ($request->startDate != '' && $request->endDate != '') {
$query->whereBetween(DB::raw('DATE(created_at)'), [$request->startDate, $request->endDate]);
}
$sortBy = 'created_at';
$sortRule = 'desc';
if ($request->sortBy != '' && $request->sortRule != '') {
$sortBy = $request->sortBy;
$sortRule = $request->sortRule;
}
$query->orderBy($sortBy, $sortRule);
return inertia('Sale/Index', [
'query' => $query->paginate(),
'customer_with_deposit' => $customer_with_deposits,
'customer_with_paylaters' => $customer_with_paylaters,
'_q' => $request->q,
'_sortBy' => $sortBy,
'_sortRule' => $sortRule,
]);
}

@ -35,6 +35,7 @@ class Sale extends Model
'format_human_created_at',
'format_created_at',
'display_amount',
'payment_with',
];
protected static function booted(): void
@ -77,6 +78,21 @@ class Sale extends Model
});
}
public function paymentWith(): Attribute
{
return Attribute::make(get: function () {
if ($this->payed_with != null) {
return [
self::PAYED_WITH_DEPOSIT => 'Deposit',
self::PAYED_WITH_PAYLATER => 'Hutang',
self::PAYED_WITH_POIN => 'Penukaran Poin',
][$this->payed_with];
}
return '';
});
}
public function create_notification()
{
if ($this->payed_with == self::PAYED_WITH_POIN) {

@ -13,13 +13,14 @@ export default function FormInputDateRanger({
<div>
{label !== '' && (
<label
htmlFor="first_name"
htmlFor={label}
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
{label}
</label>
)}
<DatePicker
id={label}
selected={converToDate(selected.startDate)}
onChange={(date) => {
let startDate = dateToString(date[0])

@ -96,7 +96,7 @@ export default function Index(props) {
<div className="w-full flex flex-col">
{/* chips */}
<div className="w-full flex flex-row overflow-y-scroll space-x-2 px-4">
<div className="w-full flex flex-row space-x-2 px-4">
<div className={isStatus(ALL)} onClick={handleAll}>
Semua
</div>

@ -90,7 +90,10 @@ export default function SidebarNav({ user }) {
</Sidebar.Item>
</Sidebar.ItemGroup>
<p className="text-sm font-light text-gray-900 dark:text-gray-100 text-center bottom-4 left-4 pt-10">
Elsoft &copy; {new Date().getFullYear()}
SesuaiHarapanDev &copy; {new Date().getFullYear()}{' '}
{new Date().getFullYear() !== 2023
? '- ' + new Date().getFullYear()
: ''}
</p>
</Sidebar.Items>
</Sidebar>

@ -19,6 +19,7 @@ export default function SelectionInput(props) {
placeholder = '',
error = '',
all = 0,
type = 'id',
} = props
const [showItems, setShowItem] = useState([])
@ -45,7 +46,11 @@ export default function SelectionInput(props) {
const handleSelectItem = (item) => {
setIsSelected(true)
onItemSelected(item.id)
if (type === 'id') {
onItemSelected(item.id)
} else {
onItemSelected(item)
}
setSelected(item.name)
setIsOpen(false)
}

@ -100,7 +100,6 @@ export default function Detail(props) {
<th scope="col" className="py-3 px-6">
Kuota
</th>
<th scope="col" className="py-3 px-6">
Masa Aktif
</th>

@ -1,27 +1,71 @@
import React, { useState, useEffect } from 'react'
import { Head, Link, router } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import { HiEye } from 'react-icons/hi'
import { HiEye, HiFilter, HiOutlineFilter } from 'react-icons/hi'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import SearchInput from '@/Components/SearchInput'
import { formatIDR } from '@/utils'
import { useModalState } from '@/hooks'
import ModalFilter from './ModalFilter'
import ThSort from '@/Components/ThSortComponent'
export default function Info(props) {
const {
query: { links, data },
customer_with_deposit,
customer_with_paylaters,
_q,
_sortBy,
_sortRule,
} = props
const [search, setSearch] = useState('')
const filterModal = useModalState()
const [search, setSearch] = useState({
q: _q,
sortBy: _sortBy,
sortRule: _sortRule,
})
const preValue = usePrevious(search)
const params = { q: search }
const handleChangeSearch = (e) => {
setSearch({
...search,
q: e.target.value,
})
}
const handleFilter = (filter) => {
setSearch({
...search,
...filter,
})
}
const sort = (key, sort = null) => {
if (sort !== null) {
setSearch({
...search,
sortBy: key,
sortRule: sort,
})
return
}
setSearch({
...search,
sortBy: key,
sortRule: search.sortRule == 'asc' ? 'desc' : 'asc',
})
}
const params = { ...search }
useEffect(() => {
if (preValue) {
router.get(
route(route().current()),
{ q: search },
{ ...search },
{
replace: true,
preserveState: true,
@ -36,16 +80,11 @@ export default function Info(props) {
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className="flex justify-between">
<div className="flex items-center">
<SearchInput
onChange={(e) => setSearch(e.target.value)}
value={search}
/>
<div className="flex flex-col gap-2 items-start md:flex-row p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 mb-2">
<div className="w-full overflow-auto">
<div className="text-lg font-bold mb-2">
Saldo Deposit
</div>
</div>
<div className="overflow-auto">
<div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
@ -54,32 +93,164 @@ export default function Info(props) {
scope="col"
className="py-3 px-6"
>
#
Nama
</th>
<th
scope="col"
className="py-3 px-6"
>
Customer
Total Beli
</th>
</tr>
</thead>
<tbody>
{customer_with_deposit.map((sale) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={sale.customer_id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white hover:underline"
>
<Link
href={route(
'customer.edit',
sale
)}
>
{sale.customer.name}
</Link>
</td>
<td
scope="row"
className="py-4 px-6"
>
Rp. {formatIDR(sale.total)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className="w-full overflow-auto">
<div className="text-lg font-bold mb-2">
Saldo Hutang
</div>
<div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th
scope="col"
className="py-3 px-6"
>
Tanggal
Nama
</th>
<th
scope="col"
className="py-3 px-6"
>
Voucher
Total Beli
</th>
</tr>
</thead>
<tbody>
{customer_with_paylaters.map((sale) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={sale.customer_id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white hover:underline"
>
<Link
href={route(
'customer.edit',
sale
)}
>
{sale.customer.name}
</Link>
</td>
<td
scope="row"
className="py-4 px-6"
>
Rp. {formatIDR(sale.total)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
<div className="p-6 overflow-hidden shadow-sm sm:rounded-lg bg-gray-200 dark:bg-gray-800 space-y-4">
<div className="flex flex-col lg:flex-row gap-1 justify-between">
<div className="flex flex-row space-x-2">
<SearchInput
onChange={handleChangeSearch}
value={search.q}
/>
</div>
<div>
<div
className="px-3 py-2 rounded-md border bg-gray-600 border-gray-700 hover:bg-gray-500"
onClick={() => filterModal.toggle()}
>
<HiOutlineFilter className="w-5 h-5 text-white" />
</div>
</div>
</div>
<div className="overflow-auto">
<div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<ThSort
sort={sort}
label={'code'}
search={search}
>
#
</ThSort>
<ThSort
sort={sort}
label={'customer_id'}
search={search}
>
Customer
</ThSort>
<ThSort
sort={sort}
label={'created_at'}
search={search}
>
Tanggal
</ThSort>
<ThSort
sort={sort}
label={'payed_with'}
search={search}
>
Pembayaran
</ThSort>
<th
scope="col"
className="py-3 px-6"
>
Total
Voucher
</th>
<ThSort
sort={sort}
label={'amount'}
search={search}
>
Total
</ThSort>
<th
scope="col"
className="py-3 px-6 w-1/8"
@ -111,6 +282,9 @@ export default function Info(props) {
<td className="py-4 px-6">
{sale.format_created_at}
</td>
<td className="py-4 px-6">
{sale.payment_with}
</td>
<td className="py-4 px-6">
{formatIDR(
sale.items_count
@ -145,6 +319,7 @@ export default function Info(props) {
</div>
</div>
</div>
<ModalFilter state={filterModal} handleFilter={handleFilter} />
</AuthenticatedLayout>
)
}

@ -0,0 +1,90 @@
import React, { useState } from 'react'
import { HiXCircle } from 'react-icons/hi2'
import {
PAYED_WITH_DEPOSIT,
PAYED_WITH_MANUAL,
PAYED_WITH_PAYLATER,
} from '@/constant'
import Modal from '@/Components/Modal'
import Button from '@/Components/Button'
import LocationSelectionInput from '../Location/SelectionInput'
import FormInputDateRanger from '@/Components/FormInputDateRange'
export default function ModalFilter(props) {
const { state, handleFilter } = props
const [locations, setLocations] = useState([])
const [dates, setDates] = useState({
startDate: null,
endDate: null,
})
const [payment, setPayment] = useState('')
const handleAddLocation = (location) => {
const isExists = locations.find((l) => l.id === location.id)
if (!isExists) {
setLocations(locations.concat(location))
}
}
const handleRemoveLocation = (location) => {
setLocations(locations.filter((l) => l.id !== location.id))
}
const handleClickFilter = () => {
handleFilter({
location_ids: locations.map((l) => l.id),
payment,
...dates,
})
state.toggle()
}
return (
<Modal isOpen={state.isOpen} toggle={state.toggle} title={'Filter'}>
{/* TODO: buat modal dengan filter, lokasi multiple, range tanggal, type pembayaran */}
<div>Lokasi: </div>
<div className="p-2 border rounded-md">
<div className="flex flex-row w-full gap-1 flex-wrap mb-2">
{locations.map((location) => (
<div
className="px-2 py-1 border rounded-md flex flex-row items-center gap-1"
key={location.id}
>
<div className="">{location.name}</div>
<div onClick={() => handleRemoveLocation(location)}>
<HiXCircle className="text-red-600 w-5 h-5" />
</div>
</div>
))}
</div>
<LocationSelectionInput
placeholder="pilih lokasi"
type="item"
itemSelected={null}
onItemSelected={(item) => handleAddLocation(item)}
/>
</div>
<div>
<FormInputDateRanger
label="Tanggal: "
selected={dates}
onChange={(dates) => setDates(dates)}
/>
</div>
<div className="mb-1 text-sm">Pembayaran : </div>
<select
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
onChange={(e) => setPayment(e.target.value)}
value={payment}
>
<option value=""> -- filter pembayaran -- </option>
<option value={PAYED_WITH_DEPOSIT}>Deposit</option>
<option value={PAYED_WITH_PAYLATER}>Hutang</option>
</select>
<Button onClick={() => handleClickFilter()}>Filter</Button>
</Modal>
)
}

@ -3,7 +3,7 @@ import { Link, router } from '@inertiajs/react'
import { usePrevious } from 'react-use'
import { Head } from '@inertiajs/react'
import { Button, Dropdown } from 'flowbite-react'
import { HiFilter, HiPencil, HiTrash } from 'react-icons/hi'
import { HiFilter, HiOutlineFilter, HiPencil, HiTrash } from 'react-icons/hi'
import { useModalState } from '@/hooks'
import { hasPermission, formatIDR } from '@/utils'
@ -182,7 +182,7 @@ export default function Index(props) {
className="px-3 py-2 rounded-md border bg-gray-600 border-gray-700 hover:bg-gray-500"
onClick={filterModal.toggle}
>
<HiFilter className="w-5 h-5 text-white" />
<HiOutlineFilter className="w-5 h-5 text-white" />
</div>
</div>
<div>

Loading…
Cancel
Save