diff --git a/app/Http/Controllers/Admin/SaleController.php b/app/Http/Controllers/Admin/SaleController.php index 2f5876f..69d9a4d 100644 --- a/app/Http/Controllers/Admin/SaleController.php +++ b/app/Http/Controllers/Admin/SaleController.php @@ -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, ]); } diff --git a/app/Models/Sale.php b/app/Models/Sale.php index 382fb11..d2533f1 100644 --- a/app/Models/Sale.php +++ b/app/Models/Sale.php @@ -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) { diff --git a/resources/js/Components/FormInputDateRange.jsx b/resources/js/Components/FormInputDateRange.jsx index fbf008e..4ba18d0 100644 --- a/resources/js/Components/FormInputDateRange.jsx +++ b/resources/js/Components/FormInputDateRange.jsx @@ -13,13 +13,14 @@ export default function FormInputDateRanger({
{label !== '' && ( )} { let startDate = dateToString(date[0]) diff --git a/resources/js/Customer/Index/Index.jsx b/resources/js/Customer/Index/Index.jsx index de4a12e..977ff5f 100644 --- a/resources/js/Customer/Index/Index.jsx +++ b/resources/js/Customer/Index/Index.jsx @@ -96,7 +96,7 @@ export default function Index(props) {
{/* chips */} -
+
Semua
diff --git a/resources/js/Layouts/Partials/SidebarNav.jsx b/resources/js/Layouts/Partials/SidebarNav.jsx index 5c21a8c..896559c 100644 --- a/resources/js/Layouts/Partials/SidebarNav.jsx +++ b/resources/js/Layouts/Partials/SidebarNav.jsx @@ -90,7 +90,10 @@ export default function SidebarNav({ user }) {

- Elsoft © {new Date().getFullYear()} + SesuaiHarapanDev © {new Date().getFullYear()}{' '} + {new Date().getFullYear() !== 2023 + ? '- ' + new Date().getFullYear() + : ''}

diff --git a/resources/js/Pages/Location/SelectionInput.jsx b/resources/js/Pages/Location/SelectionInput.jsx index 940beb6..8d5b344 100644 --- a/resources/js/Pages/Location/SelectionInput.jsx +++ b/resources/js/Pages/Location/SelectionInput.jsx @@ -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) } diff --git a/resources/js/Pages/Sale/Detail.jsx b/resources/js/Pages/Sale/Detail.jsx index b4eb0d1..8a74271 100644 --- a/resources/js/Pages/Sale/Detail.jsx +++ b/resources/js/Pages/Sale/Detail.jsx @@ -100,7 +100,6 @@ export default function Detail(props) { Kuota - Masa Aktif diff --git a/resources/js/Pages/Sale/Index.jsx b/resources/js/Pages/Sale/Index.jsx index 188292b..8a586bf 100644 --- a/resources/js/Pages/Sale/Index.jsx +++ b/resources/js/Pages/Sale/Index.jsx @@ -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) {
-
-
-
- setSearch(e.target.value)} - value={search} - /> +
+
+
+ Saldo Deposit
-
-
@@ -54,32 +93,164 @@ export default function Info(props) { scope="col" className="py-3 px-6" > - # + Nama + + + + {customer_with_deposit.map((sale) => ( + + + + + ))} + +
- Customer + Total Beli
+ + {sale.customer.name} + + + Rp. {formatIDR(sale.total)} +
+
+
+
+
+ Saldo Hutang +
+
+ + + + + + + {customer_with_paylaters.map((sale) => ( + + + + + ))} + +
- Tanggal + Nama - Voucher + Total Beli
+ + {sale.customer.name} + + + Rp. {formatIDR(sale.total)} +
+
+
+
+
+
+
+ +
+
+
filterModal.toggle()} + > + +
+
+
+
+
+ + + + + # + + + Customer + + + Tanggal + + + Pembayaran + + + Total +
- Total + Voucher {sale.format_created_at} + + {sale.payment_with} + {formatIDR( sale.items_count @@ -145,6 +319,7 @@ export default function Info(props) { + ) } diff --git a/resources/js/Pages/Sale/ModalFilter.jsx b/resources/js/Pages/Sale/ModalFilter.jsx new file mode 100644 index 0000000..0c1e491 --- /dev/null +++ b/resources/js/Pages/Sale/ModalFilter.jsx @@ -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 ( + + {/* TODO: buat modal dengan filter, lokasi multiple, range tanggal, type pembayaran */} +
Lokasi:
+
+
+ {locations.map((location) => ( +
+
{location.name}
+
handleRemoveLocation(location)}> + +
+
+ ))} +
+ handleAddLocation(item)} + /> +
+
+ setDates(dates)} + /> +
+
Pembayaran :
+ + + +
+ ) +} diff --git a/resources/js/Pages/Voucher/Index.jsx b/resources/js/Pages/Voucher/Index.jsx index dba201c..ca0601d 100644 --- a/resources/js/Pages/Voucher/Index.jsx +++ b/resources/js/Pages/Voucher/Index.jsx @@ -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} > - +