Compare commits

..

No commits in common. "b954e3cb84a03dc2ff1dbf1d414df0efd2ccc6e3" and "8ad735fbf9b52190e4ca8b0700b3f5ada4c9ed5a" have entirely different histories.

6 changed files with 47 additions and 77 deletions

View File

@ -54,7 +54,6 @@ public function share(Request $request): array
'image' => url('/assets/logo.png'), 'image' => url('/assets/logo.png'),
'url' => $request->url(), 'url' => $request->url(),
], ],
'vapidPublicKey' => config('webpush.vapid.public_key'),
]; ];
} }
} }

View File

@ -18,9 +18,9 @@
use App\Models\LeaveRequest; use App\Models\LeaveRequest;
use App\Models\Order; use App\Models\Order;
use App\Models\Payroll; use App\Models\Payroll;
use App\Models\ProductVariant;
use App\Models\Purchase; use App\Models\Purchase;
use App\Models\RawMaterialPrice; use App\Models\PurchaseItem;
use App\Models\RestockItem;
use App\Models\User; use App\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
@ -82,16 +82,10 @@ public function getAttendanceStats(?string $startDate, ?string $endDate, ?User $
]; ];
} }
$employees = Employee::whereHas( $employees = Employee::whereHas('user', fn ($q) => $q
'user',
fn($q) => $q
->where('is_active', true) ->where('is_active', true)
->whereHas( ->whereHas('roles', fn ($r) => $r
'roles', ->whereHas('permissions', fn ($p) => $p
fn($r) => $r
->whereHas(
'permissions',
fn($p) => $p
->where('name', 'attendances.create') ->where('name', 'attendances.create')
) )
) )
@ -205,42 +199,49 @@ public function getCashOverview(?string $startDate, ?string $endDate): array
public function getRawMaterialStock(): array public function getRawMaterialStock(): array
{ {
$items = RawMaterialPrice::query() $query = PurchaseItem::query()
->join('raw_materials', 'raw_material_prices.raw_material_id', '=', 'raw_materials.id') ->join('purchases', 'purchase_items.purchase_id', '=', 'purchases.id')
->select('raw_material_prices.stock', 'raw_materials.unit') ->leftJoin('raw_material_prices', 'purchase_items.raw_material_price_id', '=', 'raw_material_prices.id')
->leftJoin('raw_materials', 'raw_material_prices.raw_material_id', '=', 'raw_materials.id');
$items = $query->select('purchase_items.*', 'raw_materials.unit')
->get(); ->get();
$totalQty = $items->sum('stock'); $totalQty = $items->sum('quantity');
$totalValue = $items->sum('subtotal');
$byUnit = [ $byUnit = [
'yard' => $items->filter(fn ($i) => $i->unit === RawMaterialUnit::YARD->value)->sum('stock'), 'yard' => $items->filter(fn ($i) => $i->unit === RawMaterialUnit::YARD)->sum('quantity'),
'meter' => $items->filter(fn ($i) => $i->unit === RawMaterialUnit::METER->value)->sum('stock'), 'meter' => $items->filter(fn ($i) => $i->unit === RawMaterialUnit::METER)->sum('quantity'),
'kilogram' => $items->filter(fn ($i) => $i->unit === RawMaterialUnit::KG->value)->sum('stock'), 'kilogram' => $items->filter(fn ($i) => $i->unit === RawMaterialUnit::KG)->sum('quantity'),
]; ];
return [ return [
'total_stock' => $totalQty, 'total_stock' => $totalQty,
'total_value' => $totalValue,
'by_unit' => $byUnit, 'by_unit' => $byUnit,
]; ];
} }
public function getProductStock(): array public function getProductStock(): array
{ {
$variants = ProductVariant::query() $query = RestockItem::query()
->select('stock', 'reject_stock', 'retail_stock') ->join('restocks', 'restock_items.restock_id', '=', 'restocks.id');
$items = $query->select('restock_items.*', 'restocks.stock_type')
->get(); ->get();
$totalStock = $variants->sum('stock'); $totalQty = $items->sum('quantity');
$totalRejectStock = $variants->sum('reject_stock'); $totalValue = $items->sum('subtotal');
$totalRetailStock = $variants->sum('retail_stock');
$byType = $items->groupBy(fn ($i) => $i->stock_type ?? 'unknown')
->map(fn ($group) => $group->sum('quantity'))
->toArray();
return [ return [
'total_stock' => $totalStock + $totalRejectStock + $totalRetailStock, 'total_stock' => $totalQty,
'by_type' => [ 'total_value' => $totalValue,
'stock' => $totalStock, 'by_type' => $byType,
'reject_stock' => $totalRejectStock,
'retail_stock' => $totalRetailStock,
],
]; ];
} }
@ -381,7 +382,7 @@ public function getMonthlyRevenueByChannel(?string $startDate, ?string $endDate,
->groupBy(DB::raw("DATE_FORMAT(created_at, '%Y-%m')"), DB::raw("DATE_FORMAT(created_at, '%b %Y')")) ->groupBy(DB::raw("DATE_FORMAT(created_at, '%Y-%m')"), DB::raw("DATE_FORMAT(created_at, '%b %Y')"))
->orderBy(DB::raw("DATE_FORMAT(created_at, '%Y-%m')")) ->orderBy(DB::raw("DATE_FORMAT(created_at, '%Y-%m')"))
->get() ->get()
->map(fn($item) => [ ->map(fn ($item) => [
'month' => $item->month, 'month' => $item->month,
'store' => (int) $item->store, 'store' => (int) $item->store,
'shopee' => (int) $item->shopee, 'shopee' => (int) $item->shopee,
@ -405,7 +406,7 @@ public function getRevenueByPaymentType(?string $startDate, ?string $endDate, ?U
->selectRaw('COALESCE(SUM(total_amount), 0) as total') ->selectRaw('COALESCE(SUM(total_amount), 0) as total')
->groupBy('payment_type') ->groupBy('payment_type')
->get() ->get()
->map(fn($item) => [ ->map(fn ($item) => [
'payment_type' => $item->payment_type, 'payment_type' => $item->payment_type,
'label' => $item->payment_type->label(), 'label' => $item->payment_type->label(),
'total' => (int) $item->total, 'total' => (int) $item->total,
@ -720,7 +721,7 @@ public function getRevenueTrend(?string $startDate, ?string $endDate, ?User $use
->groupBy(DB::raw('DATE(orders.created_at)')) ->groupBy(DB::raw('DATE(orders.created_at)'))
->orderBy(DB::raw('DATE(orders.created_at)')) ->orderBy(DB::raw('DATE(orders.created_at)'))
->get() ->get()
->map(fn($item) => [ ->map(fn ($item) => [
'date' => $item->date, 'date' => $item->date,
'qty' => (int) $item->qty, 'qty' => (int) $item->qty,
]) ])
@ -811,7 +812,7 @@ public function getOrderStats(?string $startDate, ?string $endDate, ?User $user
->groupBy('marketing_id') ->groupBy('marketing_id')
->with('marketing:id') ->with('marketing:id')
->get() ->get()
->map(fn($item) => [ ->map(fn ($item) => [
'name' => $item->marketing?->userProfile->full_name ?? '-', 'name' => $item->marketing?->userProfile->full_name ?? '-',
'count' => $item->count, 'count' => $item->count,
'total' => (int) $item->total, 'total' => (int) $item->total,

View File

@ -1,5 +1,4 @@
export { default as AlertError } from './alert-error'; export { default as AlertError } from './alert-error';
export { FlashToast } from './flash-toast'; export { FlashToast } from './flash-toast';
export { NotificationBell } from './notification-bell'; export { NotificationBell } from './notification-bell';
export { NotificationPermissionPrompt } from './notification-permission-prompt';
export { PWAUpdateToast } from './pwa-update-toast'; export { PWAUpdateToast } from './pwa-update-toast';

View File

@ -1,31 +0,0 @@
import { usePage } from '@inertiajs/react';
import { useEffect, useRef } from 'react';
import { usePushNotification } from '@/hooks/use-push-notification';
export function NotificationPermissionPrompt() {
const { vapidPublicKey } = usePage().props as { vapidPublicKey?: string };
const hasRequested = useRef(false);
const { isSupported, requestPermission, subscribe } =
usePushNotification();
useEffect(() => {
if (!isSupported || hasRequested.current) {
return;
}
if (Notification.permission !== 'default') {
return;
}
hasRequested.current = true;
void requestPermission().then(async (result) => {
if (result === 'granted' && vapidPublicKey) {
await subscribe(vapidPublicKey);
}
});
}, [isSupported, requestPermission, subscribe, vapidPublicKey]);
return null;
}

View File

@ -1,4 +1,3 @@
import { NotificationPermissionPrompt } from '@/components/notifications/notification-permission-prompt';
import { Toaster } from '@/components/ui/sonner'; import { Toaster } from '@/components/ui/sonner';
import AppLayoutTemplate from '@/layouts/app/app-sidebar-layout'; import AppLayoutTemplate from '@/layouts/app/app-sidebar-layout';
import type { BreadcrumbItem } from '@/types'; import type { BreadcrumbItem } from '@/types';
@ -14,7 +13,6 @@ export default function AppLayout({
<AppLayoutTemplate breadcrumbs={breadcrumbs}> <AppLayoutTemplate breadcrumbs={breadcrumbs}>
{children} {children}
<Toaster /> <Toaster />
<NotificationPermissionPrompt />
</AppLayoutTemplate> </AppLayoutTemplate>
); );
} }

View File

@ -67,6 +67,7 @@ type AnalysisProps = {
}; };
rawMaterialStock: { rawMaterialStock: {
total_stock: number; total_stock: number;
total_value: number;
by_unit: { by_unit: {
yard: number; yard: number;
meter: number; meter: number;
@ -75,10 +76,11 @@ type AnalysisProps = {
}; };
productStock: { productStock: {
total_stock: number; total_stock: number;
total_value: number;
by_type: { by_type: {
stock?: number; good?: number;
reject_stock?: number; reject?: number;
retail_stock?: number; retail?: number;
}; };
}; };
revenueSummary: { revenueSummary: {
@ -589,6 +591,7 @@ export default function Analysis({
icon={Package} icon={Package}
mainLabel="Total Stok" mainLabel="Total Stok"
mainValue={rawMaterialStock.total_stock.toLocaleString('id-ID')} mainValue={rawMaterialStock.total_stock.toLocaleString('id-ID')}
subLabel={`Rp${formatRupiah(rawMaterialStock.total_value)}`}
description="Tidak terpengaruh filter tanggal" description="Tidak terpengaruh filter tanggal"
items={[ items={[
{ label: 'Yard', value: rawMaterialStock.by_unit?.yard?.toLocaleString('id-ID') ?? '0' }, { label: 'Yard', value: rawMaterialStock.by_unit?.yard?.toLocaleString('id-ID') ?? '0' },
@ -604,11 +607,12 @@ export default function Analysis({
icon={ShoppingCart} icon={ShoppingCart}
mainLabel="Total Stok" mainLabel="Total Stok"
mainValue={productStock.total_stock.toLocaleString('id-ID')} mainValue={productStock.total_stock.toLocaleString('id-ID')}
subLabel={`Rp${formatRupiah(productStock.total_value)}`}
description="Tidak terpengaruh filter tanggal" description="Tidak terpengaruh filter tanggal"
items={[ items={[
{ label: 'Bagus', value: (productStock.by_type?.stock ?? 0).toLocaleString('id-ID') }, { label: 'Bagus', value: (productStock.by_type?.good ?? 0).toLocaleString('id-ID') },
{ label: 'Reject', value: (productStock.by_type?.reject_stock ?? 0).toLocaleString('id-ID') }, { label: 'Reject', value: (productStock.by_type?.reject ?? 0).toLocaleString('id-ID') },
{ label: 'Ecer', value: (productStock.by_type?.retail_stock ?? 0).toLocaleString('id-ID') }, { label: 'Ecer', value: (productStock.by_type?.retail ?? 0).toLocaleString('id-ID') },
]} ]}
/> />
)} )}