dashboard done

dev
Aji Kamaludin 1 year ago
parent 8bea9fd9ea
commit d4b2d9253b
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -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]

@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use Illuminate\Http\Request;
class CustomerController extends Controller
{
public function index(Request $request)
{
$query = Customer::orderBy('updated_at', 'desc');
if ($request->q != '') {
$query->where('name', 'like', "%$request->q%")
->orWhere('fullname', 'like', "%$request->q%")
->orWhere('username', 'like', "%$request->q%");
}
return $query->get();
}
}

@ -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,
]);

@ -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()

@ -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(),

@ -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 () {

27
package-lock.json generated

@ -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",

@ -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",

@ -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 (
<div>
{label !== '' && (
<label
htmlFor="first_name"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
{label}
</label>
)}
<DatePicker
selected={converToDate(selected.startDate)}
onChange={(date) => {
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 && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{error}
</p>
)}
</div>
)
}

@ -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 (
<div className="flex flex-col items-center" ref={ref}>
<div className="w-full flex flex-col items-center">
<div className="w-full">
<div className="flex flex-col relative">
{label !== '' && (
<label
htmlFor="first_name"
className="mb-2 block text-sm font-medium text-gray-900 dark:text-white"
>
{label}
</label>
)}
<div className="w-full">
<div
className={`p-1.5 mb-2 bg-gray-50 dark:bg-gray-700 flex border rounded-lg
${
error
? 'border-red-500'
: 'border-gray-300 dark:border-gray-600'
}
${disabled ? 'bg-gray-50' : ''}`}
>
<input
className="block w-full text-sm bg-gray-50 text-gray-900 dark:border-gray-700 border cursor-pointer dark:text-gray-300 outline-none border-transparent dark:bg-gray-700 dark:placeholder-gray-400 px-1"
onMouseDown={onInputMouseDown}
placeholder={placeholder}
value={`${
isSelected
? selected === null
? ''
: selected
: query
}`}
onChange={(e) =>
filterItems(e.target.value)
}
disabled={disabled}
/>
{isSelected && (
<div
onClick={
disabled ? () => {} : removeItem
}
>
<button className="cursor-pointer w-6 h-6 text-red-300 outline-none focus:outline-none">
<HiX />
</button>
</div>
)}
<div onClick={disabled ? () => {} : toggle}>
<button className="cursor-pointer w-6 h-6 text-gray-300 outline-none focus:outline-none">
{isOpen ? (
<HiChevronUp />
) : (
<HiChevronDown />
)}
</button>
</div>
</div>
{error && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{error}
</p>
)}
</div>
{isOpen && (
<div
className="absolute mt-1 shadow-lg bg-gray-50 dark:bg-gray-700 dark:text-gray-200 top-100 z-40 w-full lef-0 rounded overflow-y-auto"
style={{ maxHeight: '300px', top: '100%' }}
>
<div className="flex flex-col w-full">
{loading ? (
<div>
<div className="flex w-full items-center p-2 pl-2 border-transparent border-l-2 relative hover:border-neutral-content">
<div className="w-full items-center justify-center flex">
<div className="mx-2 my-5">
<Spinner className="mr-2" />
<span>Loading...</span>
</div>
</div>
</div>
</div>
) : (
<>
{showItems.map((item, index) => (
<div
key={index}
onClick={() =>
handleSelectItem(item)
}
>
<div className="flex w-full items-center p-2 pl-2 border-transparent border-l-2 relative hover:border-neutral-content hover:bg-gray-400 bg-opacity-10 dark:hover:bg-gray-200 dark:hover:bg-opacity-10 dark:hover:text-gray-100">
<div className="w-full items-center flex">
<div className="mx-2">
<span>
{item.name}
</span>
</div>
</div>
</div>
</div>
))}
{showItems.length <= 0 && (
<div>
<div className="flex w-full items-center p-2 pl-2 border-transparent border-l-2 relative hover:border-neutral-content">
<div className="w-full items-center justify-center flex">
<div className="mx-2 my-5">
<span>
No Items
Found
</span>
</div>
</div>
</div>
</div>
)}
</>
)}
</div>
</div>
)}
</div>
</div>
</div>
</div>
)
}

@ -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 (
<AuthenticatedLayout
auth={props.auth}
@ -15,11 +103,197 @@ export default function Dashboard(props) {
<div>
<div className="mx-auto sm:px-6 lg:px-8 ">
<div className="overflow-hidden shadow-sm sm:rounded-lg bg-white dark:bg-gray-800">
<div className="p-6 dark:text-gray-100 ">Dashboard</div>
<div className="grid grid-cols-4 gap-2">
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Voucher
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_voucher)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Customer
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_customer)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Customer Verified
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_customer_verified)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Deposit
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_deposit)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Voucher terjual bulan {month}
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_voucher_sale_this_month)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Jumlah Voucher terjual bulan {month}
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(count_voucher_sale_this_month)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Total Voucher terjual hari ini
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(total_voucher_sale_this_day)}
</div>
</div>
<div className="border rounded-md shadow bg-white p-4 flex flex-col">
<div className="text-gray-600 text-xl">
Jumlah Voucher terjual hari ini
</div>
<div className="font-bold text-2xl pt-2">
{formatIDR(count_voucher_sale_this_day)}
</div>
</div>
</div>
<div className="w-full flex flex-row mt-4 space-x-2 border rounded-md shadow">
<div className="flex-1 overflow-auto bg-white p-4">
<div className="w-full flex flex-row justify-between mb-4">
<div className="tex-gray-500 text-xl pb-4">
Penjualan
</div>
<div className="flex flex-row space-x-1">
<LocationSelectionInput
itemSelected={location_id}
onItemSelected={(id) =>
setLocationId(id)
}
placeholder="filter lokasi"
/>
<CustomerSelectionInput
itemSelected={customer_id}
onItemSelected={(id) =>
setCustomerId(id)
}
placeholder="filter customer"
/>
<FormInputDateRanger
selected={dates}
onChange={(dates) => setDates(dates)}
/>
</div>
</div>
<Bar
options={options}
data={data}
className="max-h-96"
/>
</div>
</div>
<div className="bg-white rounded-md shadow border mt-4 w-full">
<div className="tex-gray-500 text-xl px-3 py-4">
Deposit Hari Ini
</div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4 px-2">
<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">
#
</th>
<th scope="col" className="py-3 px-6">
Customer
</th>
<th scope="col" className="py-3 px-6">
Deposit
</th>
</tr>
</thead>
<tbody>
{deposites.map((deposit, index) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={`depo${index}`}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{index + 1}
</td>
<td className="py-4 px-6">
{deposit.customer.name}
</td>
<td className="py-4 px-6">
{formatIDR(deposit.total)}
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="bg-white rounded-md shadow border mt-2">
<div className="text-gray-500 text-xl px-3 py-4">
Penjualan Hari Ini
</div>
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400 mb-4 px-2">
<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">
#
</th>
<th scope="col" className="py-3 px-6">
Customer
</th>
<th scope="col" className="py-3 px-6">
Voucher
</th>
<th scope="col" className="py-3 px-6">
Total
</th>
</tr>
</thead>
<tbody>
{sales.map((sale, index) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={`sale${index}`}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{index + 1}
</td>
<td className="py-4 px-6">
{sale.name}
</td>
<td className="py-4 px-6">
{formatIDR(sale.count)}
</td>
<td className="py-4 px-6">
{formatIDR(sale.total)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</AuthenticatedLayout>
);
)
}

@ -81,6 +81,12 @@ export default function Index(props) {
>
Deposit
</th>
<th
scope="col"
className="py-3 px-6"
>
Tanggal
</th>
<th
scope="col"
className="py-3 px-6"
@ -114,6 +120,9 @@ export default function Index(props) {
<td className="py-4 px-6">
{deposit.amount}
</td>
<td className="py-4 px-6">
{deposit.format_created_at}
</td>
<td className="py-4 px-6">
{deposit.description}
</td>

@ -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 (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
page={'Banner'}
action={'Form'}
page={`Sale`}
action={`Invoice #${sale.code}`}
>
<Head title="Banner" />
<Head title={`Invoice #${sale.code}`} />
<div>
<div className="mx-auto sm:px-6 lg:px-8">
<div className="overflow-hidden p-4 shadow-sm sm:rounded-lg bg-white dark:bg-gray-800 flex flex-col ">
<div className="text-xl font-bold mb-4">Banner</div>
<FormInput
name="title"
value={data.title}
onChange={handleOnChange}
label="Title"
error={errors.title}
/>
<FormFile
label={'Image'}
onChange={(e) =>
setData('image', e.target.files[0])
}
error={errors.image}
preview={
isEmpty(data.image_url) === false && (
<img
src={data.image_url}
className="mb-1 h-24 w-full object-cover"
alt="preview"
/>
)
}
/>
<div className="py-4">
<Suspense fallback={<div>Loading...</div>}>
<TinyEditor
value={data.description}
init={{
height: 500,
// menubar: false,
menubar:
'file edit view insert format tools table help',
plugins:
'preview importcss searchreplace autolink directionality code visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help charmap emoticons',
toolbar_mode: 'scrolling',
toolbar:
'undo redo | insertfile image media link | bold italic underline strikethrough | fontfamily fontsize blocks | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | forecolor backcolor removeformat | charmap emoticons | fullscreen preview save print | ltr rtl | anchor codesample',
}}
onEditorChange={(newValue, editor) => {
setData(
'description',
editor.getContent()
)
}}
/>
</Suspense>
</div>
<div className="mt-8">
<Button
onClick={handleSubmit}
processing={processing}
>
Simpan
</Button>
</div>
<table className="relative w-full overflow-x-auto p-2 rounded">
<tbody>
<tr>
<td className="font-bold">Customer</td>
<td>:</td>
<td>{sale.customer.name}</td>
</tr>
<tr>
<td className="font-bold">
Metode Pembayaran
</td>
<td>:</td>
<td>{sale.payed_with}</td>
</tr>
<tr>
<td className="font-bold">Total</td>
<td>:</td>
<td>{formatIDR(sale.amount)}</td>
</tr>
</tbody>
</table>
<div className="px-1 font-bold my-2">Voucher</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">
No
</th>
<th scope="col" className="py-3 px-6">
Lokasi
</th>
<th scope="col" className="py-3 px-6">
Username
</th>
<th scope="col" className="py-3 px-6">
Password
</th>
<th scope="col" className="py-3 px-6">
Profile
</th>
<th scope="col" className="py-3 px-6">
Comment
</th>
<th scope="col" className="py-3 px-6">
Kuota
</th>
</tr>
</thead>
<tbody>
{sale.items.map((item, index) => (
<tr
className="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
key={item.voucher.id}
>
<td
scope="row"
className="py-4 px-6 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
{index + 1}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.location.name}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.username}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.password}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.profile}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.comment}
</td>
<td scope="row" className="py-4 px-6">
{item.voucher.display_quota}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>

@ -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 (
<AuthenticatedLayout
auth={props.auth}
@ -30,6 +49,12 @@ export default function Info(props) {
<Button size="sm">Tambah</Button>
</Link>
)} */}
<div className="flex items-center">
<SearchInput
onChange={(e) => setSearch(e.target.value)}
value={search}
/>
</div>
</div>
<div className="overflow-auto">
<div>
@ -118,7 +143,7 @@ export default function Info(props) {
</table>
</div>
<div className="w-full flex items-center justify-center">
<Pagination links={links} />
<Pagination links={links} params={params} />
</div>
</div>
</div>

@ -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');

Loading…
Cancel
Save