add dashboard charts

pull/1/head
Aji Kamaludin 3 years ago
parent e8d1668683
commit ce306cac3d
No known key found for this signature in database
GPG Key ID: 670E1F26AD5A8099

@ -2,18 +2,59 @@
namespace App\Http\Controllers;
use DB;
use Carbon\Carbon;
use App\Models\Product;
use App\Models\Payroll;
use App\Models\PayrollItem;
use App\Models\Employee;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
public function __invoke()
public function __invoke(Request $request)
{
$now = now();
$startDate = $now->startOfWeek()->format('Y-m-d');
$endDate = $now->endOfWeek()->format('Y-m-d');
if ($request->startDate && $request->endDate) {
$startDate = Carbon::parse($request->startDate)->format('Y-m-d');
$endDate = Carbon::parse($request->endDate)->format('Y-m-d');
}
$totalAmount = Payroll::whereBetween('date', [$startDate, $endDate])->sum('recived');
$totalItem = Payroll::whereBetween('date', [$startDate, $endDate])->sum('item_count');
$charts = Payroll::selectRaw('SUM(recived) as amount, date')
->whereBetween('date', [$startDate, $endDate])
->orderBy('date', 'desc')
->groupBy('date')
->get();
$employees = Payroll::selectRaw('employee_id, SUM(recived) as amount, SUM(item_count) as count')
->whereBetween('date', [$startDate, $endDate])
->groupBy('employee_id')
->with(['employee'])
->get();
$products = PayrollItem::selectRaw('product_id, SUM(quantity) as count')
->whereBetween(DB::raw('DATE(created_at)'), [$startDate, $endDate])
->groupBy('product_id')
->with(['product'])
->get();
return inertia('Dashboard', [
'product' => Product::count(),
'employee' => Employee::count(),
'totalAmount' => $totalAmount,
'totalItem' => $totalItem,
'charts' => $charts,
'employees' => $employees,
'products' => $products,
'_startDate' => $startDate,
'_endDate' => $endDate
]);
}
}

@ -26,4 +26,21 @@ class Employee extends Model
}
return null;
}
public function payrolls()
{
return $this->hasMany(Payroll::class);
}
protected static function booted()
{
static::deleting(function ($model) {
if ($model->payrolls()->count() >= 1) {
foreach ($model->payrolls as $payroll) {
$payroll->items()->delete();
$payroll->delete();
}
}
});
}
}

@ -16,8 +16,15 @@ class PayrollItem extends Model
'price',
];
protected $cascadeDeletes = ['payroll'];
public function product()
{
return $this->belongsTo(Product::class);
}
public function payroll()
{
return $this->belongsTo(Payroll::class);
}
}

@ -26,4 +26,33 @@ class Product extends Model
}
return null;
}
public function payrollItems()
{
return $this->hasMany(PayrollItem::class);
}
public function payrolls()
{
return $this->hasManyThrough(
Payroll::class,
PayrollItem::class,
'product_id',
'id',
'id',
'payroll_id'
);
}
protected static function booted()
{
static::deleting(function ($model) {
if ($model->payrolls()->count() >= 1) {
foreach ($model->payrolls as $payroll) {
$payroll->items()->delete();
$payroll->delete();
}
}
});
}
}

@ -2,7 +2,10 @@
"name": "laravel/laravel",
"type": "project",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"keywords": [
"framework",
"laravel"
],
"license": "MIT",
"require": {
"php": "^7.3|^8.0",

27
package-lock.json generated

@ -5,9 +5,11 @@
"packages": {
"": {
"dependencies": {
"chart.js": "^3.7.0",
"daisyui": "^1.20.0",
"moment": "^2.29.1",
"qs": "^6.10.2",
"react-chartjs-2": "^4.0.0",
"react-datepicker": "^4.5.0",
"react-number-format": "^4.9.0",
"react-to-print": "^2.14.3",
@ -3011,6 +3013,11 @@
"node": "*"
}
},
"node_modules/chart.js": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.0.tgz",
"integrity": "sha512-31gVuqqKp3lDIFmzpKIrBeum4OpZsQjSIAqlOpgjosHDJZlULtvwLEZKtEhIAZc7JMPaHlYMys40Qy9Mf+1AAg=="
},
"node_modules/chokidar": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
@ -7958,6 +7965,15 @@
"node": ">=0.10.0"
}
},
"node_modules/react-chartjs-2": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.0.0.tgz",
"integrity": "sha512-0kx41EVO6wIoeU6zvdwovX9kKcdrs7O62DGTSNmwAXZeLGJ3U+n4XijO1kxcMmAi4I6PQJWGD5oRwxVixHSp6g==",
"peerDependencies": {
"chart.js": "^3.5.0",
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/react-datepicker": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.5.0.tgz",
@ -12450,6 +12466,11 @@
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=",
"dev": true
},
"chart.js": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.0.tgz",
"integrity": "sha512-31gVuqqKp3lDIFmzpKIrBeum4OpZsQjSIAqlOpgjosHDJZlULtvwLEZKtEhIAZc7JMPaHlYMys40Qy9Mf+1AAg=="
},
"chokidar": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
@ -16136,6 +16157,12 @@
"object-assign": "^4.1.1"
}
},
"react-chartjs-2": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.0.0.tgz",
"integrity": "sha512-0kx41EVO6wIoeU6zvdwovX9kKcdrs7O62DGTSNmwAXZeLGJ3U+n4XijO1kxcMmAi4I6PQJWGD5oRwxVixHSp6g==",
"requires": {}
},
"react-datepicker": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.5.0.tgz",

@ -27,9 +27,11 @@
"tailwindcss": "^3.0.0"
},
"dependencies": {
"chart.js": "^3.7.0",
"daisyui": "^1.20.0",
"moment": "^2.29.1",
"qs": "^6.10.2",
"react-chartjs-2": "^4.0.0",
"react-datepicker": "^4.5.0",
"react-number-format": "^4.9.0",
"react-to-print": "^2.14.3",

44
public/css/app.css vendored

@ -1711,6 +1711,11 @@ html {
--tw-bg-opacity: 1;
background-color: hsla(var(--b2) / var(--tw-bg-opacity, 1));
}
.table-compact td,.table-compact th {
font-size: .875rem;
line-height: 1.25rem;
padding: .5rem;
}
.textarea-bordered {
--tw-border-opacity: 0.2;
}
@ -1979,6 +1984,12 @@ html {
.max-h-screen {
max-height: 100vh;
}
.max-h-96 {
max-height: 24rem;
}
.max-h-11 {
max-height: 2.75rem;
}
.min-h-screen {
min-height: 100vh;
}
@ -2118,10 +2129,10 @@ html {
margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
}
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
.space-x-0 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
margin-right: calc(0px * var(--tw-space-x-reverse));
margin-left: calc(0px * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
@ -2227,6 +2238,10 @@ html {
--tw-border-opacity: 1;
border-color: rgb(0 0 0 / var(--tw-border-opacity));
}
.border-b-gray-400 {
--tw-border-opacity: 1;
border-bottom-color: rgb(156 163 175 / var(--tw-border-opacity));
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
@ -2355,6 +2370,9 @@ html {
.pl-2 {
padding-left: 0.5rem;
}
.pb-4 {
padding-bottom: 1rem;
}
.text-left {
text-align: left;
}
@ -3064,14 +3082,6 @@ html {
width: 41.666667%;
}
.md\:w-2\/3 {
width: 66.666667%;
}
.md\:w-1\/3 {
width: 33.333333%;
}
.md\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@ -3084,18 +3094,18 @@ html {
align-items: stretch;
}
.md\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0px * var(--tw-space-y-reverse));
}
.md\:space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.md\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0px * var(--tw-space-y-reverse));
}
.md\:border-t-0 {
border-top-width: 0px;
}

13915
public/js/app.js vendored

File diff suppressed because it is too large Load Diff

@ -1,9 +1,69 @@
import React from 'react';
import Authenticated from '@/Layouts/Authenticated';
import { Head } from '@inertiajs/inertia-react';
import React, { useState, useEffect } from 'react'
import { usePrevious } from 'react-use'
import { Inertia } from '@inertiajs/inertia'
import DatePicker from 'react-datepicker'
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from 'chart.js'
import { Bar } from 'react-chartjs-2'
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip)
import Authenticated from '@/Layouts/Authenticated'
import { Head } from '@inertiajs/inertia-react'
import { formatIDR } from '@/utils'
export default function Dashboard(props) {
const { employee, product } = props
const {
employee,
product,
totalAmount,
totalItem,
employees,
products,
charts,
_startDate,
_endDate,
} = props
const [startDate, setStartDate] = useState(
_startDate ? new Date(_startDate) : new Date()
)
const [endDate, setEndDate] = useState(
_endDate ? new Date(_endDate) : new Date()
)
const preValue = usePrevious(`${startDate}-${endDate}`)
const options = {
responsive: true,
};
const data = {
labels: charts.map((item) => item.date),
datasets: [
{
label: 'Sales',
data: charts.map((item) => item.amount),
backgroundColor: 'rgb(87, 13, 248, 0.5)', //rgb(87, 13, 248, 0.5) //rgba(255, 99, 132, 0.5)
},
],
}
useEffect(() => {
if (preValue) {
Inertia.get(route(route().current()), {startDate, endDate}, {
replace: true,
preserveState: true,
})
}
}, [startDate, endDate])
return (
<Authenticated
auth={props.auth}
@ -17,7 +77,7 @@ export default function Dashboard(props) {
<Head title="Dashboard" />
<div className="py-12">
<div className="flex flex-row sm:px-6 lg:px-8 space-x-4">
<div className="flex flex-col md:flex-row sm:px-6 lg:px-8 space-x-0 md:space-x-4 space-y-2 md:space-y-0">
<div className="bg-white overflow-hidden shadow-sm rounded-lg w-full">
<div className="py-4 px-4 font-bold text-xl">
<div className="text-4xl">{product}</div>
@ -30,6 +90,158 @@ export default function Dashboard(props) {
Karyawan
</div>
</div>
<div className="bg-white overflow-hidden shadow-sm rounded-lg w-full">
<div className="py-4 px-4 font-bold text-xl">
<div className="text-4xl">
Rp. {formatIDR(totalAmount)}
</div>
Minggu ini
</div>
</div>
<div className="bg-white overflow-hidden shadow-sm rounded-lg w-full">
<div className="py-4 px-4 font-bold text-xl">
<div className="text-4xl">
{formatIDR(totalItem)}
</div>
Minggu ini
</div>
</div>
</div>
<div className="flex flex-col md:flex-row sm:px-6 lg:px-8 space-x-0 md:space-x-4 space-y-2 md:space-y-0 mt-4">
<div className="bg-white overflow-hidden shadow-sm rounded-lg w-full">
<div className="flex flex-col md:flex-row justify-between pt-4 px-4 font-bold text-xl">
<div className="my-auto">Gajian Minggu ini</div>
<div>
<div className="flex flex-row md:space-x-4">
<div>
<label className="label">
<span className="label-text">
Tanggal Awal
</span>
</label>
<div className="relative">
<DatePicker
selected={startDate}
onChange={(date) => {
setStartDate(date)
}}
format="dd/mm/yyyy"
className="input input-bordered"
nextMonthButtonLabel=">"
previousMonthButtonLabel="<"
/>
<div className="absolute right-2.5 rounded-l-none y-0 flex items-center top-2.5">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
</div>
</div>
</div>
<div>
<label className="label">
<span className="label-text">
Tanggal Akhir
</span>
</label>
<div className="relative">
<DatePicker
selected={endDate}
onChange={(date) => {
setEndDate(date)
}}
format="dd/mm/yyyy"
className="input input-bordered"
nextMonthButtonLabel=">"
previousMonthButtonLabel="<"
/>
<div className="absolute right-2.5 rounded-l-none y-0 flex items-center top-2.5">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="px-4 pb-4">
<Bar options={options} data={data} className='max-h-96' />;
</div>
</div>
</div>
<div className="flex flex-col md:flex-row sm:px-6 lg:px-8 space-x-0 md:space-x-4 space-y-2 md:space-y-0 mt-4">
<div className="bg-white overflow-hidden shadow-sm rounded-lg w-full">
<div className="py-4 px-4 font-bold text-xl border-b-2 border-b-gray-400">
Kerjaan karyawan minggu ini
</div>
<div className="overflow-x-auto">
<table className="table w-full table-compact">
<thead>
<tr>
<th>Nama</th>
<th>Quantity</th>
<th>Grand Total</th>
</tr>
</thead>
<tbody>
{employees.map((item) => (
<tr
key={`employee-${item.employee_id}`}
>
<td>{item.employee.name}</td>
<td>{formatIDR(item.count)}</td>
<td>{formatIDR(item.amount)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className="bg-white overflow-hidden shadow-sm rounded-lg w-full">
<div className="py-4 px-4 font-bold text-xl border-b-2 border-b-gray-400">
Produk Masuk minggu ini
</div>
<div className="overflow-x-auto">
<table className="table w-full table-compact">
<thead>
<tr>
<th>Nama</th>
<th>Quantity</th>
</tr>
</thead>
<tbody>
{products.map((item) => (
<tr key={`product-${item.product_id}`}>
<td>{item.product.name}</td>
<td>{formatIDR(item.count)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</Authenticated>

Loading…
Cancel
Save