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 ( +
+ {error} +
+ )} ++ {error} +
+ )} ++ # + | ++ Customer + | ++ Deposit + | +
---|---|---|
+ {index + 1} + | ++ {deposit.customer.name} + | ++ {formatIDR(deposit.total)} + | +
+ # + | ++ Customer + | ++ Voucher + | ++ Total + | +
---|---|---|---|
+ {index + 1} + | ++ {sale.name} + | ++ {formatIDR(sale.count)} + | ++ {formatIDR(sale.total)} + | +