From d4b2d9253bd11b0a68ca6a66df4f68a86e3e478d Mon Sep 17 00:00:00 2001
From: Aji Kamaludin
Date: Wed, 7 Jun 2023 01:02:48 +0700
Subject: [PATCH] dashboard done
---
TODO.md | 4 +-
.../Controllers/Api/CustomerController.php | 23 ++
.../Controllers/Customer/CartController.php | 4 +-
app/Http/Controllers/GeneralController.php | 89 +++++-
app/Http/Controllers/SaleController.php | 14 +-
app/Models/SaleItem.php | 5 +
package-lock.json | 27 ++
package.json | 2 +
.../js/Components/FormInputDateRange.jsx | 56 ++++
.../js/Pages/Customer/SelectionInput.jsx | 263 ++++++++++++++++
resources/js/Pages/Dashboard.jsx | 286 +++++++++++++++++-
resources/js/Pages/DepositHistory/Index.jsx | 9 +
resources/js/Pages/Sale/Detail.jsx | 187 ++++++------
resources/js/Pages/Sale/Index.jsx | 29 +-
routes/api.php | 2 +
15 files changed, 885 insertions(+), 115 deletions(-)
create mode 100644 app/Http/Controllers/Api/CustomerController.php
create mode 100644 resources/js/Components/FormInputDateRange.jsx
create mode 100644 resources/js/Pages/Customer/SelectionInput.jsx
diff --git a/TODO.md b/TODO.md
index a9a8967..6a82e0f 100644
--- a/TODO.md
+++ b/TODO.md
@@ -16,8 +16,8 @@
- [x] List Customer Verification
- [x] Manual Approve Verification -> mendapatkan limit hutang
- [x] Setting Bonus Coin (memasukan amount bonus coin yang didapat dengan level dan harga voucher) - bonus coin
-- [ ] Voucher Sales (index: customer, total, jumlah voucher, detail: customer, list voucher, payment)
-- [ ] Dashboard (gafik hasil penjualan : disorting tanggal, lokasi dan customer)
+- [x] Voucher Sales (index: customer, total, jumlah voucher, detail: customer, list voucher, payment)
+- [x] Dashboard (gafik hasil penjualan : disorting tanggal, lokasi dan customer)
[total voucher] [total customer] [total customer verified] [total deposit]
[total voucher terjual bulan xxx] [jumlah voucher terjual bulan xxx] [total voucher terjual hari ini ] [jumlah voucher terjual hari ini]
[cart penjualan per tanggal, pilih range tanggal (range default 7), bisa filter by lokasi dan customer]
diff --git a/app/Http/Controllers/Api/CustomerController.php b/app/Http/Controllers/Api/CustomerController.php
new file mode 100644
index 0000000..b2f6bfe
--- /dev/null
+++ b/app/Http/Controllers/Api/CustomerController.php
@@ -0,0 +1,23 @@
+q != '') {
+ $query->where('name', 'like', "%$request->q%")
+ ->orWhere('fullname', 'like', "%$request->q%")
+ ->orWhere('username', 'like', "%$request->q%");
+ }
+
+ return $query->get();
+ }
+}
diff --git a/app/Http/Controllers/Customer/CartController.php b/app/Http/Controllers/Customer/CartController.php
index f875d87..65302c8 100644
--- a/app/Http/Controllers/Customer/CartController.php
+++ b/app/Http/Controllers/Customer/CartController.php
@@ -178,7 +178,7 @@ class CartController extends Controller
$deposit = $customer->deposites()->create([
'credit' => $customer->deposit_balance,
'description' => $description,
- 'related_type' => $sale::class,
+ 'related_type' => Sale::class,
'related_id' => $sale->id,
'is_valid' => DepositHistory::STATUS_VALID,
]);
@@ -200,7 +200,7 @@ class CartController extends Controller
$deposit = $customer->deposites()->create([
'credit' => $total,
'description' => $description,
- 'related_type' => $sale::class,
+ 'related_type' => Voucher::class,
'related_id' => $sale->id,
'is_valid' => DepositHistory::STATUS_VALID,
]);
diff --git a/app/Http/Controllers/GeneralController.php b/app/Http/Controllers/GeneralController.php
index 4ea2aa5..14f747b 100644
--- a/app/Http/Controllers/GeneralController.php
+++ b/app/Http/Controllers/GeneralController.php
@@ -2,14 +2,99 @@
namespace App\Http\Controllers;
+use App\Models\Customer;
+use App\Models\DepositHistory;
+use App\Models\Sale;
+use App\Models\SaleItem;
+use App\Models\Voucher;
use Illuminate\Http\Request;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class GeneralController extends Controller
{
- public function index()
+ public function index(Request $request)
{
- return inertia('Dashboard');
+ $total_voucher = Voucher::count();
+ $total_customer = Customer::count();
+ $total_customer_verified = Customer::where('identity_verified', Customer::VERIFIED)->count();
+ $total_deposit = DepositHistory::where('is_valid', DepositHistory::STATUS_VALID)->sum('debit');
+
+ $month = now()->locale('id')->translatedFormat('F');
+ $startOfMonth = now()->startOfMonth()->format('m/d/Y');
+ $endOfMonth = now()->endOfMonth()->format('m/d/Y');
+ $total_voucher_sale_this_month = SaleItem::whereBetween(DB::raw("strftime('%m/%d/%Y', created_at)"), [$startOfMonth, $endOfMonth])
+ ->sum('price');
+ $count_voucher_sale_this_month = SaleItem::whereBetween(DB::raw("strftime('%m/%d/%Y', created_at)"), [$startOfMonth, $endOfMonth])
+ ->sum('quantity');
+ $total_voucher_sale_this_day = SaleItem::whereDate('created_at', now()->format('Y-m-d'))
+ ->sum('price');
+ $count_voucher_sale_this_day = SaleItem::whereDate('created_at', now()->format('Y-m-d'))
+ ->sum('quantity');
+
+ $deposites = DepositHistory::whereDate('created_at', now()->format('Y-m-d'))
+ ->where('is_valid', DepositHistory::STATUS_VALID)
+ ->where('debit', '!=', '0')
+ ->groupBy('customer_id')
+ ->orderBy('updated_at', 'desc')
+ ->with('customer')
+ ->limit(20)
+ ->selectRaw('sum(debit) as total, is_valid, customer_id')
+ ->get();
+
+ $sales = SaleItem::whereDate('sale_items.created_at', now()->format('Y-m-d'))
+ ->join('sales', 'sales.id', '=', 'sale_items.sale_id')
+ ->join('customers', 'customers.id', '=', 'sales.customer_id')
+ ->groupBy('sales.customer_id')
+ ->orderBy('sale_items.updated_at', 'desc')
+ ->limit(20)
+ ->selectRaw('sum(sale_items.price) as total, sum(quantity) as count, sales.customer_id, customers.name, entity_id')
+ ->get();
+
+ // charts
+ $startDate = now()->startOfMonth()->format('m/d/Y');
+ $endDate = now()->endOfMonth()->format('m/d/Y');
+ if ($request->start_date != '') {
+ $startDate = Carbon::parse($request->start_date)->format('m/d/Y');
+ }
+ if ($request->end_date != '') {
+ $endDate = Carbon::parse($request->end_date)->format('m/d/Y');
+ }
+ $charts = Sale::selectRaw("SUM(amount) as sale_total, strftime('%d/%m/%Y', date_time) as date")
+ ->whereBetween(DB::raw("strftime('%m/%d/%Y', date_time)"), [$startDate, $endDate])
+ ->orderBy('date_time', 'asc')
+ ->groupBy(DB::raw("strftime('%m/%d/%Y', date_time)"));
+
+ // filter lokasi
+ if ($request->location_id != '') {
+ $charts->whereHas('items', function ($q) use ($request) {
+ $q->join('vouchers', 'vouchers.id', '=', 'sale_items.entity_id')
+ ->where('vouchers.location_id', $request->location_id);
+ });
+ }
+
+ // filter customer
+ if ($request->customer_id != '') {
+ $charts->where('customer_id', $request->customer_id);
+ }
+
+ return inertia('Dashboard', [
+ 'total_voucher' => $total_voucher,
+ 'total_customer' => $total_customer,
+ 'total_customer_verified' => $total_customer_verified,
+ 'total_deposit' => $total_deposit,
+ 'total_voucher_sale_this_month' => $total_voucher_sale_this_month,
+ 'count_voucher_sale_this_month' => $count_voucher_sale_this_month,
+ 'total_voucher_sale_this_day' => $total_voucher_sale_this_day,
+ 'count_voucher_sale_this_day' => $count_voucher_sale_this_day,
+ 'month' => $month,
+ 'deposites' => $deposites,
+ 'sales' => $sales,
+ 'charts' => $charts->get(),
+ '_startDate' => $startDate,
+ '_endDate' => $endDate,
+ ]);
}
public function maintance()
diff --git a/app/Http/Controllers/SaleController.php b/app/Http/Controllers/SaleController.php
index 4735389..0f69215 100644
--- a/app/Http/Controllers/SaleController.php
+++ b/app/Http/Controllers/SaleController.php
@@ -7,10 +7,20 @@ use Illuminate\Http\Request;
class SaleController extends Controller
{
- public function index()
+ public function index(Request $request)
{
$query = Sale::with(['items.voucher', 'customer.level'])
- ->withCount(['items']);
+ ->withCount(['items'])
+ ->orderBy('updated_at', 'desc');
+
+ if ($request->q != '') {
+ $query->where('code', 'like', "%$request->q%")
+ ->orWhereHas('customer', function ($query) use ($request) {
+ $query->where('name', 'like', "%$request->q%")
+ ->orWhere('fullname', 'like', "%$request->q%")
+ ->orWhere('username', 'like', "%$request->q%");
+ });
+ }
return inertia('Sale/Index', [
'query' => $query->paginate(),
diff --git a/app/Models/SaleItem.php b/app/Models/SaleItem.php
index e5362fa..c0aa2d0 100644
--- a/app/Models/SaleItem.php
+++ b/app/Models/SaleItem.php
@@ -29,6 +29,11 @@ class SaleItem extends Model
return $this->belongsTo(Voucher::class, 'entity_id');
}
+ public function sale()
+ {
+ return $this->belongsTo(Sale::class, 'sale_id');
+ }
+
public function shareWord(): Attribute
{
return Attribute::make(get: function () {
diff --git a/package-lock.json b/package-lock.json
index 9badb38..a98ff39 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,12 +6,14 @@
"": {
"dependencies": {
"@tinymce/tinymce-react": "^4.3.0",
+ "chart.js": "^4.3.0",
"flowbite": "^1.6.3",
"flowbite-react": "^0.3.8",
"is": "^3.3.0",
"moment": "^2.29.4",
"nprogress": "^0.2.0",
"qs": "^6.11.0",
+ "react-chartjs-2": "^5.2.0",
"react-datepicker": "^4.8.0",
"react-icons": "^4.7.1",
"react-number-format": "^5.1.2",
@@ -879,6 +881,11 @@
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
+ "node_modules/@kurkle/color": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
+ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1188,6 +1195,17 @@
"node": ">=4"
}
},
+ "node_modules/chart.js": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz",
+ "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=7"
+ }
+ },
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -2352,6 +2370,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-chartjs-2": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz",
+ "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==",
+ "peerDependencies": {
+ "chart.js": "^4.1.1",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-datepicker": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.12.0.tgz",
diff --git a/package.json b/package.json
index 3f97a79..9f2c8e9 100644
--- a/package.json
+++ b/package.json
@@ -21,12 +21,14 @@
},
"dependencies": {
"@tinymce/tinymce-react": "^4.3.0",
+ "chart.js": "^4.3.0",
"flowbite": "^1.6.3",
"flowbite-react": "^0.3.8",
"is": "^3.3.0",
"moment": "^2.29.4",
"nprogress": "^0.2.0",
"qs": "^6.11.0",
+ "react-chartjs-2": "^5.2.0",
"react-datepicker": "^4.8.0",
"react-icons": "^4.7.1",
"react-number-format": "^5.1.2",
diff --git a/resources/js/Components/FormInputDateRange.jsx b/resources/js/Components/FormInputDateRange.jsx
new file mode 100644
index 0000000..fbf008e
--- /dev/null
+++ b/resources/js/Components/FormInputDateRange.jsx
@@ -0,0 +1,56 @@
+import React from 'react'
+import DatePicker from 'react-datepicker'
+import { converToDate, dateToString } from '@/utils'
+
+export default function FormInputDateRanger({
+ selected,
+ onChange,
+ label = '',
+ error,
+ placeholder = '',
+}) {
+ return (
+
+ {label !== '' && (
+
+ )}
+
{
+ let startDate = dateToString(date[0])
+ let endDate = null
+ if (date[1] != null) {
+ endDate = dateToString(date[1])
+ }
+ onChange({ startDate, endDate })
+ }}
+ startDate={converToDate(selected.startDate)}
+ endDate={converToDate(selected.endDate)}
+ closeOnScroll={true}
+ shouldCloseOnSelect={true}
+ dateFormat="dd/MM/yyyy"
+ className={`mb-2 bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-gray-700 dark:placeholder-gray-400 dark:text-white ${
+ error
+ ? 'border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500 dark:focus:ring-red-500 dark:focus:border-red-500'
+ : 'border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500'
+ }`}
+ nextMonthButtonLabel=">"
+ previousMonthButtonLabel="<"
+ nextYearButtonLabel=">"
+ previousYearButtonLabel="<"
+ placeholderText={placeholder}
+ selectsRange
+ />
+ {error && (
+
+ {error}
+
+ )}
+
+ )
+}
diff --git a/resources/js/Pages/Customer/SelectionInput.jsx b/resources/js/Pages/Customer/SelectionInput.jsx
new file mode 100644
index 0000000..f7d5d8c
--- /dev/null
+++ b/resources/js/Pages/Customer/SelectionInput.jsx
@@ -0,0 +1,263 @@
+import React, { useRef, useEffect, useState } from 'react'
+import { useDebounce } from '@/hooks'
+import { usePage } from '@inertiajs/react'
+import axios from 'axios'
+import { HiChevronDown, HiChevronUp, HiX } from 'react-icons/hi'
+import { Spinner } from 'flowbite-react'
+
+export default function SelectionInput(props) {
+ const ref = useRef()
+ const {
+ props: { auth },
+ } = usePage()
+
+ const {
+ label = '',
+ itemSelected = null,
+ onItemSelected = () => {},
+ disabled = false,
+ placeholder = '',
+ error = '',
+ all = 0,
+ } = props
+
+ const [showItems, setShowItem] = useState([])
+
+ const [isSelected, setIsSelected] = useState(true)
+ const [selected, setSelected] = useState(null)
+
+ const [query, setQuery] = useState('')
+ const q = useDebounce(query, 300)
+
+ const [isOpen, setIsOpen] = useState(false)
+ const [loading, setLoading] = useState(false)
+
+ const toggle = () => {
+ setQuery('')
+ setIsOpen(!isOpen)
+ }
+
+ const onInputMouseDown = () => {
+ setIsSelected(false)
+ setQuery('')
+ setIsOpen(!isOpen)
+ }
+
+ const handleSelectItem = (item) => {
+ setIsSelected(true)
+ onItemSelected(item.id)
+ setSelected(item.name)
+ setIsOpen(false)
+ }
+
+ const removeItem = () => {
+ setIsSelected(false)
+ setSelected('')
+ onItemSelected(null)
+ }
+
+ const filterItems = (value) => {
+ setIsSelected(false)
+ setQuery(value)
+ }
+
+ useEffect(() => {
+ if (isOpen === true) {
+ const checkIfClickedOutside = (e) => {
+ if (isOpen && ref.current && !ref.current.contains(e.target)) {
+ setIsOpen(false)
+ if (selected !== null) {
+ setIsSelected(true)
+ }
+ }
+ }
+ document.addEventListener('mousedown', checkIfClickedOutside)
+ return () => {
+ document.removeEventListener('mousedown', checkIfClickedOutside)
+ }
+ }
+ }, [isOpen])
+
+ const fetch = (q = '') => {
+ setLoading(true)
+ axios
+ .get(route('api.customer.index', { q: q, all: all }), {
+ headers: {
+ 'Content-Type': 'application/json',
+ // 'Authorization': 'Bearer ' + auth.user.jwt_token
+ },
+ })
+ .then((response) => {
+ setShowItem(response.data)
+ })
+ .catch((err) => {
+ alert(err)
+ })
+ .finally(() => setLoading(false))
+ }
+
+ // every select item open
+ useEffect(() => {
+ if (isOpen) {
+ fetch(q)
+ }
+ }, [q, isOpen])
+
+ // once page load
+ useEffect(() => {
+ fetch()
+ }, [])
+
+ useEffect(() => {
+ if (disabled) {
+ setSelected('')
+ }
+ }, [disabled])
+
+ useEffect(() => {
+ if (itemSelected !== null) {
+ const item = showItems.find((item) => item.id === itemSelected)
+ if (item) {
+ setSelected(item.name)
+ setIsSelected(true)
+ }
+ return
+ }
+ setIsSelected(false)
+ }, [itemSelected, loading])
+
+ useEffect(() => {
+ if (isSelected && selected === '') {
+ setSelected('')
+ setIsSelected(false)
+ }
+ }, [isSelected])
+
+ return (
+
+
+
+
+ {label !== '' && (
+
+ )}
+
+
+
+ filterItems(e.target.value)
+ }
+ disabled={disabled}
+ />
+ {isSelected && (
+
{} : removeItem
+ }
+ >
+
+
+ )}
+
{} : toggle}>
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {isOpen && (
+
+
+ {loading ? (
+
+ ) : (
+ <>
+ {showItems.map((item, index) => (
+
+ handleSelectItem(item)
+ }
+ >
+
+
+ ))}
+ {showItems.length <= 0 && (
+
+
+
+
+
+ No Items
+ Found
+
+
+
+
+
+ )}
+ >
+ )}
+
+
+ )}
+
+
+
+
+ )
+}
diff --git a/resources/js/Pages/Dashboard.jsx b/resources/js/Pages/Dashboard.jsx
index 8da35b7..255e7de 100644
--- a/resources/js/Pages/Dashboard.jsx
+++ b/resources/js/Pages/Dashboard.jsx
@@ -1,8 +1,96 @@
-import React from 'react';
-import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
-import { Head } from '@inertiajs/react';
+import React, { useState, useEffect } from 'react'
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ ArcElement,
+ Legend,
+} from 'chart.js'
+import { Bar, Doughnut } from 'react-chartjs-2'
+import { Head, router } from '@inertiajs/react'
+import { usePrevious } from 'react-use'
+import moment from 'moment'
+import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
+import { formatIDR } from '@/utils'
+import FormInputDateRanger from '@/Components/FormInputDateRange'
+import CustomerSelectionInput from './Customer/SelectionInput'
+import LocationSelectionInput from './Location/SelectionInput'
+
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ ArcElement,
+ Legend
+)
export default function Dashboard(props) {
+ const {
+ total_voucher,
+ total_customer,
+ total_customer_verified,
+ total_deposit,
+ total_voucher_sale_this_month,
+ count_voucher_sale_this_month,
+ total_voucher_sale_this_day,
+ count_voucher_sale_this_day,
+ month,
+ deposites,
+ sales,
+ charts,
+ _startDate,
+ _endDate,
+ } = props
+
+ const [dates, setDates] = useState({
+ startDate: _startDate,
+ endDate: _endDate,
+ })
+ const [customer_id, setCustomerId] = useState(null)
+ const [location_id, setLocationId] = useState(null)
+ const preValue = usePrevious(`${dates}${customer_id}${location_id}`)
+
+ const options = {
+ responsive: true,
+ scales: {
+ x: {},
+ },
+ }
+
+ const data = {
+ labels: charts.map((item) => moment(item.date).format('DD MMM YYYY')),
+ datasets: [
+ {
+ label: 'Penjualan',
+ data: charts.map((item) => item.sale_total),
+ backgroundColor: ['rgba(255, 205, 86, 1)'],
+ },
+ ],
+ }
+
+ useEffect(() => {
+ if (preValue) {
+ router.get(
+ route(route().current()),
+ {
+ startDate: dates.startDate,
+ endDate: dates.endDate,
+ customer_id,
+ location_id,
+ },
+ {
+ replace: true,
+ preserveState: true,
+ }
+ )
+ }
+ }, [dates, customer_id, location_id])
+
return (
-
-
Dashboard
+
+
+
+ Total Voucher
+
+
+ {formatIDR(total_voucher)}
+
+
+
+
+ Total Customer
+
+
+ {formatIDR(total_customer)}
+
+
+
+
+ Total Customer Verified
+
+
+ {formatIDR(total_customer_verified)}
+
+
+
+
+ Total Deposit
+
+
+ {formatIDR(total_deposit)}
+
+
+
+
+ Total Voucher terjual bulan {month}
+
+
+ {formatIDR(total_voucher_sale_this_month)}
+
+
+
+
+ Jumlah Voucher terjual bulan {month}
+
+
+ {formatIDR(count_voucher_sale_this_month)}
+
+
+
+
+ Total Voucher terjual hari ini
+
+
+ {formatIDR(total_voucher_sale_this_day)}
+
+
+
+
+ Jumlah Voucher terjual hari ini
+
+
+ {formatIDR(count_voucher_sale_this_day)}
+
+
+
+
+
+
+
+ Penjualan
+
+
+
+
+ setLocationId(id)
+ }
+ placeholder="filter lokasi"
+ />
+
+ setCustomerId(id)
+ }
+ placeholder="filter customer"
+ />
+ setDates(dates)}
+ />
+
+
+
+
+
+
+
+ Deposit Hari Ini
+
+
+
+
+
+ #
+ |
+
+ Customer
+ |
+
+ Deposit
+ |
+
+
+
+ {deposites.map((deposit, index) => (
+
+
+ {index + 1}
+ |
+
+ {deposit.customer.name}
+ |
+
+ {formatIDR(deposit.total)}
+ |
+
+ ))}
+
+
+
+
+
+ Penjualan Hari Ini
+
+
+
+
+
+ #
+ |
+
+ Customer
+ |
+
+ Voucher
+ |
+
+ Total
+ |
+
+
+
+ {sales.map((sale, index) => (
+
+
+ {index + 1}
+ |
+
+ {sale.name}
+ |
+
+ {formatIDR(sale.count)}
+ |
+
+ {formatIDR(sale.total)}
+ |
+
+ ))}
+
+
- );
+ )
}
diff --git a/resources/js/Pages/DepositHistory/Index.jsx b/resources/js/Pages/DepositHistory/Index.jsx
index bf98321..e93d1a9 100644
--- a/resources/js/Pages/DepositHistory/Index.jsx
+++ b/resources/js/Pages/DepositHistory/Index.jsx
@@ -81,6 +81,12 @@ export default function Index(props) {
>
Deposit
+
+ Tanggal
+ |
{deposit.amount}
+ |
+ {deposit.format_created_at}
+ |
{deposit.description}
|
diff --git a/resources/js/Pages/Sale/Detail.jsx b/resources/js/Pages/Sale/Detail.jsx
index e9c9ed9..328a2ee 100644
--- a/resources/js/Pages/Sale/Detail.jsx
+++ b/resources/js/Pages/Sale/Detail.jsx
@@ -6,117 +6,106 @@ import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import { Head, useForm } from '@inertiajs/react'
import FormFile from '@/Components/FormFile'
+import { formatIDR } from '@/utils'
-const TinyEditor = React.lazy(() => import('@/Components/TinyMCE'))
-
-export default function Form(props) {
- const { banner } = props
-
- const { data, setData, post, processing, errors } = useForm({
- title: '',
- description: '',
- image: '',
- image_url: '',
- })
-
- const handleOnChange = (event) => {
- setData(
- event.target.name,
- event.target.type === 'checkbox'
- ? event.target.checked
- ? 1
- : 0
- : event.target.value
- )
- }
- const handleSubmit = () => {
- if (isEmpty(banner) === false) {
- post(route('banner.update', banner))
- return
- }
- post(route('banner.store'))
- }
-
- useEffect(() => {
- if (isEmpty(banner) === false) {
- setData({
- title: banner.title,
- description: banner.description,
- image_url: banner.image_url,
- })
- }
- }, [banner])
+export default function Detail(props) {
+ const { sale } = props
return (
-
+
-
Banner
-
-
- setData('image', e.target.files[0])
- }
- error={errors.image}
- preview={
- isEmpty(data.image_url) === false && (
-
- )
- }
- />
-
-
- Loading...
}>
- {
- setData(
- 'description',
- editor.getContent()
- )
- }}
- />
-
-
-
-
-
+
+
+
+ Customer |
+ : |
+ {sale.customer.name} |
+
+
+
+ Metode Pembayaran
+ |
+ : |
+ {sale.payed_with} |
+
+
+ Total |
+ : |
+ {formatIDR(sale.amount)} |
+
+
+
+
Voucher
+
+
+
+
+ No
+ |
+
+ Lokasi
+ |
+
+ Username
+ |
+
+ Password
+ |
+
+ Profile
+ |
+
+ Comment
+ |
+
+ Kuota
+ |
+
+
+
+ {sale.items.map((item, index) => (
+
+
+ {index + 1}
+ |
+
+ {item.voucher.location.name}
+ |
+
+ {item.voucher.username}
+ |
+
+ {item.voucher.password}
+ |
+
+ {item.voucher.profile}
+ |
+
+ {item.voucher.comment}
+ |
+
+ {item.voucher.display_quota}
+ |
+
+ ))}
+
+
diff --git a/resources/js/Pages/Sale/Index.jsx b/resources/js/Pages/Sale/Index.jsx
index abb9389..b2ab0eb 100644
--- a/resources/js/Pages/Sale/Index.jsx
+++ b/resources/js/Pages/Sale/Index.jsx
@@ -1,16 +1,35 @@
-import React from 'react'
+import React, { useState, useEffect } from 'react'
import { Head, router } from '@inertiajs/react'
import { HiEye } from 'react-icons/hi'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
+import SearchInput from '@/Components/SearchInput'
import { formatIDR } from '@/utils'
+import { usePrevious } from 'react-use'
export default function Info(props) {
const {
query: { links, data },
} = props
+ const [search, setSearch] = useState('')
+ const preValue = usePrevious(search)
+
+ const params = { q: search }
+ useEffect(() => {
+ if (preValue) {
+ router.get(
+ route(route().current()),
+ { q: search },
+ {
+ replace: true,
+ preserveState: true,
+ }
+ )
+ }
+ }, [search])
+
return (
Tambah
)} */}
+
+ setSearch(e.target.value)}
+ value={search}
+ />
+
@@ -118,7 +143,7 @@ export default function Info(props) {
diff --git a/routes/api.php b/routes/api.php
index 1e1d5b2..db50ee6 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -3,6 +3,7 @@
use App\Http\Controllers\Api\LocationController;
use App\Http\Controllers\Api\RoleController;
use App\Http\Controllers\Customer\DepositController;
+use App\Http\Controllers\Api\CustomerController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
@@ -24,6 +25,7 @@ Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
// general
Route::get('/roles', [RoleController::class, 'index'])->name('api.role.index');
Route::get('/locations', [LocationController::class, 'index'])->name('api.location.index');
+Route::get('/customers', [CustomerController::class, 'index'])->name('api.customer.index');
// midtrans
Route::post('mindtrans/notification', [DepositController::class, 'mindtrans_notification'])->name('api.midtrans.notification');