merge
This commit is contained in:
commit
2cce89b6c4
176
app/Http/Controllers/LaporanController.php
Normal file
176
app/Http/Controllers/LaporanController.php
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Transaksi;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Carbon\CarbonPeriod;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
class LaporanController extends Controller
|
||||||
|
{
|
||||||
|
public function ringkasan(Request $request)
|
||||||
|
{
|
||||||
|
$filter = $request->query('filter', 'bulan');
|
||||||
|
$page = $request->query('page', 1);
|
||||||
|
|
||||||
|
$allSalesNames = Transaksi::select('nama_sales')->distinct()->pluck('nama_sales');
|
||||||
|
|
||||||
|
if ($filter === 'hari') {
|
||||||
|
return $this->laporanHarian($page, $allSalesNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->laporanBulanan($page, $allSalesNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function laporanHarian(int $page, Collection $allSalesNames)
|
||||||
|
{
|
||||||
|
$perPage = 7;
|
||||||
|
|
||||||
|
$endDate = Carbon::today()->subDays(($page - 1) * $perPage);
|
||||||
|
$startDate = $endDate->copy()->subDays($perPage - 1);
|
||||||
|
|
||||||
|
$transaksis = Transaksi::with('itemTransaksi.item.produk')
|
||||||
|
->whereBetween('created_at', [$startDate->startOfDay(), $endDate->endOfDay()])
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$transaksisByDay = $transaksis->groupBy(function ($transaksi) {
|
||||||
|
return Carbon::parse($transaksi->created_at)->format('Y-m-d');
|
||||||
|
});
|
||||||
|
|
||||||
|
$period = CarbonPeriod::create($startDate, $endDate);
|
||||||
|
$laporan = [];
|
||||||
|
|
||||||
|
foreach ($period as $date) {
|
||||||
|
$dateString = $date->format('Y-m-d');
|
||||||
|
$tanggalFormatted = $date->isoFormat('dddd, D MMMM Y');
|
||||||
|
|
||||||
|
if (isset($transaksisByDay[$dateString])) {
|
||||||
|
$transaksisPerTanggal = $transaksisByDay[$dateString];
|
||||||
|
|
||||||
|
$salesDataTransaksi = $transaksisPerTanggal->groupBy('nama_sales')
|
||||||
|
->map(fn ($transaksisPerSales) => $this->hitungDataSales($transaksisPerSales));
|
||||||
|
|
||||||
|
$fullSalesData = $allSalesNames->map(function ($namaSales) use ($salesDataTransaksi) {
|
||||||
|
return $salesDataTransaksi->get($namaSales) ?? $this->defaultSalesData($namaSales);
|
||||||
|
});
|
||||||
|
|
||||||
|
$totalItem = $fullSalesData->sum('item_terjual');
|
||||||
|
$totalBerat = $fullSalesData->sum('berat_terjual_raw');
|
||||||
|
$totalPendapatan = $fullSalesData->sum('pendapatan_raw');
|
||||||
|
|
||||||
|
$laporan[$dateString] = [
|
||||||
|
'tanggal' => $tanggalFormatted,
|
||||||
|
'total_item_terjual' => $totalItem > 0 ? $totalItem : '-',
|
||||||
|
'total_berat' => $totalBerat > 0 ? number_format($totalBerat, 2, ',', '.') . 'g' : '-',
|
||||||
|
'total_pendapatan' => $totalPendapatan > 0 ? 'Rp' . number_format($totalPendapatan, 2, ',', '.') : '-',
|
||||||
|
'sales' => $this->formatSalesDataValues($fullSalesData)->values(),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$laporan[$dateString] = [
|
||||||
|
'tanggal' => $tanggalFormatted,
|
||||||
|
'total_item_terjual' => '-',
|
||||||
|
'total_berat' => '-',
|
||||||
|
'total_pendapatan' => '-',
|
||||||
|
'sales' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$totalHariUntukPaginasi = 365;
|
||||||
|
$paginatedData = new LengthAwarePaginator(
|
||||||
|
array_reverse(array_values($laporan)),
|
||||||
|
$totalHariUntukPaginasi,
|
||||||
|
$perPage,
|
||||||
|
$page,
|
||||||
|
['path' => request()->url(), 'query' => request()->query()]
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json($paginatedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function laporanBulanan(int $page, Collection $allSalesNames)
|
||||||
|
{
|
||||||
|
$perPage = 12;
|
||||||
|
|
||||||
|
$transaksis = Transaksi::with('itemTransaksi.item.produk')
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$laporan = $transaksis->groupBy(function ($transaksi) {
|
||||||
|
return Carbon::parse($transaksi->created_at)->format('F Y');
|
||||||
|
})
|
||||||
|
->map(function ($transaksisPerTanggal, $tanggal) use ($allSalesNames) {
|
||||||
|
|
||||||
|
$salesDataTransaksi = $transaksisPerTanggal
|
||||||
|
->groupBy('nama_sales')
|
||||||
|
->map(fn ($transaksisPerSales) => $this->hitungDataSales($transaksisPerSales));
|
||||||
|
|
||||||
|
$fullSalesData = $allSalesNames->map(function ($namaSales) use ($salesDataTransaksi) {
|
||||||
|
return $salesDataTransaksi->get($namaSales) ?? $this->defaultSalesData($namaSales);
|
||||||
|
});
|
||||||
|
|
||||||
|
$totalItem = $fullSalesData->sum('item_terjual');
|
||||||
|
$totalBerat = $fullSalesData->sum('berat_terjual_raw');
|
||||||
|
$totalPendapatan = $fullSalesData->sum('pendapatan_raw');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'tanggal' => $tanggal,
|
||||||
|
'total_item_terjual' => $totalItem > 0 ? $totalItem : '-',
|
||||||
|
'total_berat' => $totalBerat > 0 ? number_format($totalBerat, 2, ',', '.') . 'g' : '-',
|
||||||
|
'total_pendapatan' => $totalPendapatan > 0 ? 'Rp' . number_format($totalPendapatan, 2, ',', '.') : '-',
|
||||||
|
'sales' => $this->formatSalesDataValues($fullSalesData)->values(),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
$paginatedData = new LengthAwarePaginator(
|
||||||
|
$laporan->forPage($page, $perPage)->values(),
|
||||||
|
$laporan->count(),
|
||||||
|
$perPage,
|
||||||
|
$page,
|
||||||
|
['path' => request()->url(), 'query' => request()->query()]
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json($paginatedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hitungDataSales(Collection $transaksisPerSales): array
|
||||||
|
{
|
||||||
|
$itemTerjual = $transaksisPerSales->sum(fn ($t) => $t->itemTransaksi->count());
|
||||||
|
$beratTerjual = $transaksisPerSales->sum(fn ($t) =>
|
||||||
|
$t->itemTransaksi->sum(fn ($it) => $it->item->produk->berat ?? 0)
|
||||||
|
);
|
||||||
|
$pendapatan = $transaksisPerSales->sum('total_harga');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'nama' => $transaksisPerSales->first()->nama_sales,
|
||||||
|
'item_terjual' => $itemTerjual,
|
||||||
|
'berat_terjual_raw' => $beratTerjual,
|
||||||
|
'pendapatan_raw' => $pendapatan,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function defaultSalesData(string $namaSales): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'nama' => $namaSales,
|
||||||
|
'item_terjual' => 0,
|
||||||
|
'berat_terjual_raw' => 0,
|
||||||
|
'pendapatan_raw' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatSalesDataValues(Collection $salesData): Collection
|
||||||
|
{
|
||||||
|
return $salesData->map(function ($sale) {
|
||||||
|
$sale['item_terjual'] = $sale['item_terjual'] > 0 ? $sale['item_terjual'] : '-';
|
||||||
|
$sale['berat_terjual'] = $sale['berat_terjual_raw'] > 0 ? number_format($sale['berat_terjual_raw'], 2, ',', '.') . 'g' : '-';
|
||||||
|
$sale['pendapatan'] = $sale['pendapatan_raw'] > 0 ? 'Rp' . number_format($sale['pendapatan_raw'], 2, ',', '.') : '-';
|
||||||
|
|
||||||
|
unset($sale['berat_terjual_raw'], $sale['pendapatan_raw']);
|
||||||
|
return $sale;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ class TransaksiFactory extends Factory
|
|||||||
$sales = Sales::inRandomOrder()->first();
|
$sales = Sales::inRandomOrder()->first();
|
||||||
$kasir = User::inRandomOrder()->first();
|
$kasir = User::inRandomOrder()->first();
|
||||||
|
|
||||||
|
$date = $this->faker->dateTimeBetween('-3 months');
|
||||||
return [
|
return [
|
||||||
'id_kasir' => $kasir?->id,
|
'id_kasir' => $kasir?->id,
|
||||||
'id_sales' => $sales?->id,
|
'id_sales' => $sales?->id,
|
||||||
@ -31,7 +32,8 @@ class TransaksiFactory extends Factory
|
|||||||
'alamat' => $this->faker->address(),
|
'alamat' => $this->faker->address(),
|
||||||
'ongkos_bikin' => $this->faker->randomFloat(2, 0, 1000000),
|
'ongkos_bikin' => $this->faker->randomFloat(2, 0, 1000000),
|
||||||
'total_harga' => $this->faker->randomFloat(2, 100000, 5000000),
|
'total_harga' => $this->faker->randomFloat(2, 100000, 5000000),
|
||||||
'created_at' => now(),
|
'created_at' => $date,
|
||||||
|
'updated_at' => $date,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
@input="$emit('update:modelValue', $event.target.value)"
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
class="mt-1 block w-full rounded-md shadow-sm sm:text-sm bg-A text-D border-B focus:border-C focus:ring focus:ring-D focus:ring-opacity-50 p-2"
|
class="mt-1 block w-full rounded-md shadow-sm sm:text-sm bg-A text-D border-B focus:border-C focus:ring focus:outline-none focus:ring-D focus:ring-opacity-50 p-2"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
46
resources/js/components/InputPassword.vue
Normal file
46
resources/js/components/InputPassword.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<div class="relative mb-8">
|
||||||
|
<input
|
||||||
|
:type="showPassword ? 'text' : 'password'"
|
||||||
|
:value="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
|
class="mt-1 block w-full rounded-md shadow-sm sm:text-sm
|
||||||
|
bg-A text-D border-B focus:border-C
|
||||||
|
focus:ring focus:ring-D focus:ring-opacity-50 p-2 pr-10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Tombol show/hide password -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="togglePassword"
|
||||||
|
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-gray-700 focus:outline-none"
|
||||||
|
>
|
||||||
|
<i v-if="showPassword" class="fas fa-eye"></i>
|
||||||
|
<i v-else class="fas fa-eye-slash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: "Password",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
|
const showPassword = ref(false);
|
||||||
|
|
||||||
|
const togglePassword = () => {
|
||||||
|
showPassword.value = !showPassword.value;
|
||||||
|
};
|
||||||
|
</script>
|
@ -6,7 +6,7 @@
|
|||||||
<label class="block text-sm font-medium text-D">Kode Item *</label>
|
<label class="block text-sm font-medium text-D">Kode Item *</label>
|
||||||
<div class="flex flex-row justify-between mt-1 w-full rounded-md bg-A shadow-sm sm:text-sm border-B">
|
<div class="flex flex-row justify-between mt-1 w-full rounded-md bg-A shadow-sm sm:text-sm border-B">
|
||||||
<input type="text" v-model="kodeItem" @keyup.enter="inputItem" placeholder="Scan atau masukkan kode item"
|
<input type="text" v-model="kodeItem" @keyup.enter="inputItem" placeholder="Scan atau masukkan kode item"
|
||||||
class=" bg-A focus:border-C focus:ring focus:ring-D focus:ring-opacity-50 p-2 w-full" />
|
class=" bg-A focus:outline-none focus:border-C focus:ring focus:ring-D focus:ring-opacity-50 p-2 w-full rounded-l-md" />
|
||||||
<button v-if="!loadingItem" @click="inputItem" class="px-3 bg-D hover:bg-D/80 text-A rounded-r-md"><i
|
<button v-if="!loadingItem" @click="inputItem" class="px-3 bg-D hover:bg-D/80 text-A rounded-r-md"><i
|
||||||
class="fas fa-arrow-right"></i></button>
|
class="fas fa-arrow-right"></i></button>
|
||||||
<div v-else class="flex items-center justify-center px-3">
|
<div v-else class="flex items-center justify-center px-3">
|
||||||
|
241
resources/js/components/RingkasanLaporanA.vue
Normal file
241
resources/js/components/RingkasanLaporanA.vue
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-row items-center justify-end mt-5 gap-3">
|
||||||
|
<div class="relative w-32" ref="filterDropdownRef">
|
||||||
|
<button @click="isFilterOpen = !isFilterOpen" type="button"
|
||||||
|
class="flex items-center justify-between w-full px-3 py-2 text-sm text-left bg-C border rounded-md border-C hover:bg-C/80 focus:outline-none">
|
||||||
|
<span>{{ selectedFilterLabel }}</span>
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
<div v-if="isFilterOpen" class="absolute z-10 w-full mt-1 bg-C border rounded-md shadow-lg border-C">
|
||||||
|
<ul class="py-1">
|
||||||
|
<li v-for="option in filterOptions" :key="option.value" @click="selectFilter(option)"
|
||||||
|
class="px-3 py-2 text-sm cursor-pointer text-D hover:bg-B">
|
||||||
|
{{ option.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative w-40" ref="exportDropdownRef">
|
||||||
|
<button @click="isExportOpen = !isExportOpen" type="button"
|
||||||
|
class="flex items-center justify-between w-full px-3 py-2 text-sm text-left bg-C border rounded-md border-C hover:bg-C/80 focus:outline-none">
|
||||||
|
<span :class="{ 'text-D': !exportFormat }">{{ selectedExportLabel }}</span>
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
<div v-if="isExportOpen" class="absolute z-10 w-full mt-1 bg-C border rounded-md shadow-lg border-C">
|
||||||
|
<ul class="py-1">
|
||||||
|
<li v-for="option in exportOptions" :key="option.value" @click="selectExport(option)"
|
||||||
|
class="px-3 py-2 text-sm cursor-pointer text-D hover:bg-B">
|
||||||
|
{{ option.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5">
|
||||||
|
<table class="w-full border-collapse border border-C rounded-md">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-C text-D rounded-t-md">
|
||||||
|
<th class="border-x border-C px-3 py-3">Tanggal</th>
|
||||||
|
<th class="border-x border-C px-3 py-3">Nama Sales</th>
|
||||||
|
<th class="border-x border-C px-3 py-3">Jumlah Item Terjual</th>
|
||||||
|
<th class="border-x border-C px-3 py-3">Total Berat Terjual</th>
|
||||||
|
<th class="border-x border-C px-3 py-3">Total Pendapatan</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-if="loading">
|
||||||
|
<td colspan="5" class="p-4">
|
||||||
|
<div class="flex items-center justify-center w-full h-30">
|
||||||
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-D"></div>
|
||||||
|
<span class="ml-2 text-gray-600">Memuat data...</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else-if="!ringkasanLaporan.length">
|
||||||
|
<td colspan="5" class="p-4 text-center">Tidak ada data untuk ditampilkan.</td>
|
||||||
|
</tr>
|
||||||
|
<template v-else v-for="item in ringkasanLaporan" :key="item.tanggal">
|
||||||
|
<template v-if="item.sales && item.sales.length > 0">
|
||||||
|
<tr class="text-center border-y border-C"
|
||||||
|
:class="item.sales[0].item_terjual == 0 ? 'bg-red-200 hover:bg-red-300' : 'hover:bg-A'">
|
||||||
|
<td class="px-3 py-2 border-x border-C bg-white" :rowspan="item.sales.length">{{
|
||||||
|
item.tanggal }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C text-left">{{ item.sales[0].nama }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ item.sales[0].item_terjual }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ item.sales[0].berat_terjual }}</td>
|
||||||
|
<td class="flex justify-center">
|
||||||
|
<div :ref="el => { if (el) pendapatanElements.push(el) }" :style="pendapatanStyle" :class="item.sales[0].pendapatan == '-' ? 'text-center' : 'text-right'">
|
||||||
|
{{ item.sales[0].pendapatan }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="sales in item.sales.slice(1)" :key="sales.nama"
|
||||||
|
class="text-center border-y border-C"
|
||||||
|
:class="sales.item_terjual == '-' ? 'bg-red-200 hover:bg-red-300' : 'hover:bg-A'">
|
||||||
|
<td class="px-3 py-2 text-left border-x border-C">{{ sales.nama }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ sales.item_terjual }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ sales.berat_terjual }}</td>
|
||||||
|
<td class="flex justify-center">
|
||||||
|
<div :ref="el => { if (el) pendapatanElements.push(el) }" :style="pendapatanStyle" :class="sales.pendapatan == '-' ? 'text-center' : 'text-right'">
|
||||||
|
{{ sales.pendapatan }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="font-semibold text-center border-y border-C bg-B hover:bg-C/80">
|
||||||
|
<td class="px-3 py-2 border-x border-C" colspan="2">Total</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ item.total_item_terjual }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ item.total_berat }}</td>
|
||||||
|
<td class="flex justify-center">
|
||||||
|
<div :ref="el => { if (el) pendapatanElements.push(el) }" :style="pendapatanStyle" :class="item.sales[0].pendapatan == '-' ? 'text-center' : 'text-right'">
|
||||||
|
{{ item.total_pendapatan }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<tr class="text-center border-y border-C hover:bg-A">
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ item.tanggal }}</td>
|
||||||
|
<td colspan="4" class="px-3 py-2 italic text-gray-500 border-x border-C bg-yellow-50 hover:bg-yellow-100">Tidak ada transaksi
|
||||||
|
pada hari ini</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div v-if="pagination.total > 0 && pagination.last_page > 1" class="flex items-center justify-end gap-2 mt-4">
|
||||||
|
<button @click="goToPage(pagination.current_page - 1)" :disabled="pagination.current_page === 1 || loading"
|
||||||
|
class="px-2 py-1 text-sm font-medium border rounded-md bg-C border-C disabled:opacity-50 disabled:cursor-not-allowed hover:bg-C/80">
|
||||||
|
Sebelumnya
|
||||||
|
</button>
|
||||||
|
<span class="text-sm text-D">
|
||||||
|
Halaman {{ pagination.current_page }} dari {{ pagination.last_page }}
|
||||||
|
</span>
|
||||||
|
<button @click="goToPage(pagination.current_page + 1)"
|
||||||
|
:disabled="(pagination.current_page === pagination.last_page) || loading"
|
||||||
|
class="px-2 py-1 text-sm font-medium border rounded-md bg-C border-C disabled:opacity-50 disabled:cursor-not-allowed hover:bg-C/80">
|
||||||
|
Berikutnya
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted, watch, computed, nextTick } from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
// --- State ---
|
||||||
|
const isFilterOpen = ref(false);
|
||||||
|
const isExportOpen = ref(false);
|
||||||
|
const filterDropdownRef = ref(null);
|
||||||
|
const exportDropdownRef = ref(null);
|
||||||
|
|
||||||
|
const filterOptions = ref([
|
||||||
|
{ value: 'bulan', label: 'Bulanan' },
|
||||||
|
{ value: 'hari', label: 'Harian' }
|
||||||
|
]);
|
||||||
|
const exportOptions = ref([
|
||||||
|
{ value: 'pdf', label: 'Pdf' },
|
||||||
|
{ value: 'xls', label: 'Excel' },
|
||||||
|
{ value: 'csv', label: 'Csv' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filterRingkasan = ref("bulan");
|
||||||
|
const exportFormat = ref(null);
|
||||||
|
const ringkasanLaporan = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const pagination = ref({
|
||||||
|
current_page: 1,
|
||||||
|
last_page: 1,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pendapatanWidth = ref(0);
|
||||||
|
const pendapatanElements = ref([]);
|
||||||
|
|
||||||
|
// --- Computed ---
|
||||||
|
const selectedFilterLabel = computed(() => {
|
||||||
|
return filterOptions.value.find(opt => opt.value === filterRingkasan.value)?.label;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedExportLabel = computed(() => {
|
||||||
|
return exportOptions.value.find(opt => opt.value === exportFormat.value)?.label || 'Export Laporan';
|
||||||
|
});
|
||||||
|
|
||||||
|
const pendapatanStyle = computed(() => ({
|
||||||
|
minWidth: `${pendapatanWidth.value}px`,
|
||||||
|
padding: '0.5rem 0.75rem'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// --- Watchers ---
|
||||||
|
watch(ringkasanLaporan, async (newValue) => {
|
||||||
|
if (newValue && newValue.length > 0) {
|
||||||
|
await nextTick();
|
||||||
|
let maxWidth = 0;
|
||||||
|
pendapatanElements.value.forEach(el => {
|
||||||
|
if (el && el.scrollWidth > maxWidth) {
|
||||||
|
maxWidth = el.scrollWidth;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pendapatanWidth.value = maxWidth;
|
||||||
|
}
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
// --- Methods ---
|
||||||
|
const fetchRingkasan = async (page = 1) => {
|
||||||
|
loading.value = true;
|
||||||
|
pendapatanElements.value = [];
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/api/laporan?filter=${filterRingkasan.value}&page=${page}`);
|
||||||
|
ringkasanLaporan.value = response.data.data;
|
||||||
|
pagination.value = {
|
||||||
|
current_page: response.data.current_page,
|
||||||
|
last_page: response.data.last_page,
|
||||||
|
total: response.data.total,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching laporan:", error);
|
||||||
|
ringkasanLaporan.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToPage = (page) => {
|
||||||
|
if (page >= 1 && page <= pagination.value.last_page) {
|
||||||
|
fetchRingkasan(page);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectFilter = (option) => {
|
||||||
|
filterRingkasan.value = option.value;
|
||||||
|
isFilterOpen.value = false;
|
||||||
|
goToPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectExport = (option) => {
|
||||||
|
exportFormat.value = option.value;
|
||||||
|
isExportOpen.value = false;
|
||||||
|
alert(`Fitur Belum dikerjakan. Laporan akan diekspor dalam format ${option.label}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDropdownsOnClickOutside = (event) => {
|
||||||
|
if (filterDropdownRef.value && !filterDropdownRef.value.contains(event.target)) {
|
||||||
|
isFilterOpen.value = false;
|
||||||
|
}
|
||||||
|
if (exportDropdownRef.value && !exportDropdownRef.value.contains(event.target)) {
|
||||||
|
isExportOpen.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Lifecycle Hooks ---
|
||||||
|
onMounted(() => {
|
||||||
|
fetchRingkasan(pagination.value.current_page);
|
||||||
|
document.addEventListener('click', closeDropdownsOnClickOutside);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('click', closeDropdownsOnClickOutside);
|
||||||
|
});
|
||||||
|
</script>
|
239
resources/js/components/RingkasanLaporanB.vue
Normal file
239
resources/js/components/RingkasanLaporanB.vue
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-row items-center justify-end mt-5 gap-3">
|
||||||
|
<div class="relative w-32" ref="filterDropdownRef">
|
||||||
|
<button @click="isFilterOpen = !isFilterOpen" type="button"
|
||||||
|
class="flex items-center justify-between w-full px-3 py-2 text-sm text-left bg-C border rounded-md border-C hover:bg-C/80 focus:outline-none">
|
||||||
|
<span>{{ selectedFilterLabel }}</span>
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
<div v-if="isFilterOpen" class="absolute z-10 w-full mt-1 bg-C border rounded-md shadow-lg border-C">
|
||||||
|
<ul class="py-1">
|
||||||
|
<li v-for="option in filterOptions" :key="option.value" @click="selectFilter(option)"
|
||||||
|
class="px-3 py-2 text-sm cursor-pointer text-D hover:bg-B">
|
||||||
|
{{ option.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative w-40" ref="exportDropdownRef">
|
||||||
|
<button @click="isExportOpen = !isExportOpen" type="button"
|
||||||
|
class="flex items-center justify-between w-full px-3 py-2 text-sm text-left bg-C border rounded-md border-C hover:bg-C/80 focus:outline-none">
|
||||||
|
<span :class="{ 'text-D': !exportFormat }">{{ selectedExportLabel }}</span>
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
<div v-if="isExportOpen" class="absolute z-10 w-full mt-1 bg-C border rounded-md shadow-lg border-C">
|
||||||
|
<ul class="py-1">
|
||||||
|
<li v-for="option in exportOptions" :key="option.value" @click="selectExport(option)"
|
||||||
|
class="px-3 py-2 text-sm cursor-pointer text-D hover:bg-B">
|
||||||
|
{{ option.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5">
|
||||||
|
<table class="w-full border-collapse border border-C rounded-md">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-C text-D rounded-t-md">
|
||||||
|
<th class="border-x border-C px-3 py-3">Tanggal</th>
|
||||||
|
<th class="border-x border-C px-3 py-3">Nama Sales</th>
|
||||||
|
<th class="border-x border-C px-3 py-3">Jumlah Item Terjual</th>
|
||||||
|
<th class="border-x border-C px-3 py-3">Total Berat Terjual</th>
|
||||||
|
<th class="border-x border-C px-3 py-3">Total Pendapatan</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-if="loading">
|
||||||
|
<td colspan="5" class="p-4">
|
||||||
|
<div class="flex items-center justify-center w-full h-30">
|
||||||
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-D"></div>
|
||||||
|
<span class="ml-2 text-gray-600">Memuat data...</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else-if="!ringkasanLaporan.length">
|
||||||
|
<td colspan="5" class="p-4 text-center">Tidak ada data untuk ditampilkan.</td>
|
||||||
|
</tr>
|
||||||
|
<template v-else v-for="item in ringkasanLaporan" :key="item.tanggal">
|
||||||
|
<template v-if="item.sales && item.sales.length > 0">
|
||||||
|
<tr class="text-center border-y border-C hover:bg-A">
|
||||||
|
<td class="px-3 py-2 border-x border-C bg-white" :rowspan="item.sales.length">{{
|
||||||
|
item.tanggal }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C text-left">{{ item.sales[0].nama }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ item.sales[0].item_terjual }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ item.sales[0].berat_terjual }}</td>
|
||||||
|
<td class="flex justify-center">
|
||||||
|
<div :ref="el => { if (el) pendapatanElements.push(el) }" :style="pendapatanStyle" :class="item.sales[0].pendapatan == '-' ? 'text-center' : 'text-right'">
|
||||||
|
{{ item.sales[0].pendapatan }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="sales in item.sales.slice(1)" :key="sales.nama"
|
||||||
|
class="text-center border-y border-C hover:bg-A">
|
||||||
|
<td class="px-3 py-2 text-left border-x border-C">{{ sales.nama }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ sales.item_terjual }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ sales.berat_terjual }}</td>
|
||||||
|
<td class="flex justify-center">
|
||||||
|
<div :ref="el => { if (el) pendapatanElements.push(el) }" :style="pendapatanStyle" :class="sales.pendapatan == '-' ? 'text-center' : 'text-right'">
|
||||||
|
{{ sales.pendapatan }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="font-semibold text-center border-y border-C bg-B hover:bg-C/80">
|
||||||
|
<td class="px-3 py-2 border-x border-C" colspan="2">Total</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ item.total_item_terjual }}</td>
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ item.total_berat }}</td>
|
||||||
|
<td class="flex justify-center">
|
||||||
|
<div :ref="el => { if (el) pendapatanElements.push(el) }" :style="pendapatanStyle" :class="item.sales[0].pendapatan == '-' ? 'text-center' : 'text-right'">
|
||||||
|
{{ item.total_pendapatan }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<tr class="text-center border-y border-C hover:bg-A">
|
||||||
|
<td class="px-3 py-2 border-x border-C">{{ item.tanggal }}</td>
|
||||||
|
<td colspan="4" class="px-3 py-2 italic text-gray-500 border-x border-C">Tidak ada transaksi
|
||||||
|
pada hari ini</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div v-if="pagination.total > 0 && pagination.last_page > 1" class="flex items-center justify-end gap-2 mt-4">
|
||||||
|
<button @click="goToPage(pagination.current_page - 1)" :disabled="pagination.current_page === 1 || loading"
|
||||||
|
class="px-2 py-1 text-sm font-medium border rounded-md bg-C border-C disabled:opacity-50 disabled:cursor-not-allowed hover:bg-C/80">
|
||||||
|
Sebelumnya
|
||||||
|
</button>
|
||||||
|
<span class="text-sm text-D">
|
||||||
|
Halaman {{ pagination.current_page }} dari {{ pagination.last_page }}
|
||||||
|
</span>
|
||||||
|
<button @click="goToPage(pagination.current_page + 1)"
|
||||||
|
:disabled="(pagination.current_page === pagination.last_page) || loading"
|
||||||
|
class="px-2 py-1 text-sm font-medium border rounded-md bg-C border-C disabled:opacity-50 disabled:cursor-not-allowed hover:bg-C/80">
|
||||||
|
Berikutnya
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted, watch, computed, nextTick } from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
// --- State ---
|
||||||
|
const isFilterOpen = ref(false);
|
||||||
|
const isExportOpen = ref(false);
|
||||||
|
const filterDropdownRef = ref(null);
|
||||||
|
const exportDropdownRef = ref(null);
|
||||||
|
|
||||||
|
const filterOptions = ref([
|
||||||
|
{ value: 'bulan', label: 'Bulanan' },
|
||||||
|
{ value: 'hari', label: 'Harian' }
|
||||||
|
]);
|
||||||
|
const exportOptions = ref([
|
||||||
|
{ value: 'pdf', label: 'Pdf' },
|
||||||
|
{ value: 'xls', label: 'Excel' },
|
||||||
|
{ value: 'csv', label: 'Csv' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filterRingkasan = ref("bulan");
|
||||||
|
const exportFormat = ref(null);
|
||||||
|
const ringkasanLaporan = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const pagination = ref({
|
||||||
|
current_page: 1,
|
||||||
|
last_page: 1,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pendapatanWidth = ref(0);
|
||||||
|
const pendapatanElements = ref([]);
|
||||||
|
|
||||||
|
// --- Computed ---
|
||||||
|
const selectedFilterLabel = computed(() => {
|
||||||
|
return filterOptions.value.find(opt => opt.value === filterRingkasan.value)?.label;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedExportLabel = computed(() => {
|
||||||
|
return exportOptions.value.find(opt => opt.value === exportFormat.value)?.label || 'Export Laporan';
|
||||||
|
});
|
||||||
|
|
||||||
|
const pendapatanStyle = computed(() => ({
|
||||||
|
minWidth: `${pendapatanWidth.value}px`,
|
||||||
|
padding: '0.5rem 0.75rem'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// --- Watchers ---
|
||||||
|
watch(ringkasanLaporan, async (newValue) => {
|
||||||
|
if (newValue && newValue.length > 0) {
|
||||||
|
await nextTick();
|
||||||
|
let maxWidth = 0;
|
||||||
|
pendapatanElements.value.forEach(el => {
|
||||||
|
if (el && el.scrollWidth > maxWidth) {
|
||||||
|
maxWidth = el.scrollWidth;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pendapatanWidth.value = maxWidth;
|
||||||
|
}
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
// --- Methods ---
|
||||||
|
const fetchRingkasan = async (page = 1) => {
|
||||||
|
loading.value = true;
|
||||||
|
pendapatanElements.value = [];
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/api/laporan?filter=${filterRingkasan.value}&page=${page}`);
|
||||||
|
ringkasanLaporan.value = response.data.data;
|
||||||
|
pagination.value = {
|
||||||
|
current_page: response.data.current_page,
|
||||||
|
last_page: response.data.last_page,
|
||||||
|
total: response.data.total,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching laporan:", error);
|
||||||
|
ringkasanLaporan.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToPage = (page) => {
|
||||||
|
if (page >= 1 && page <= pagination.value.last_page) {
|
||||||
|
fetchRingkasan(page);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectFilter = (option) => {
|
||||||
|
filterRingkasan.value = option.value;
|
||||||
|
isFilterOpen.value = false;
|
||||||
|
goToPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectExport = (option) => {
|
||||||
|
exportFormat.value = option.value;
|
||||||
|
isExportOpen.value = false;
|
||||||
|
alert(`Fitur Belum dikerjakan. Laporan akan diekspor dalam format ${option.label}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDropdownsOnClickOutside = (event) => {
|
||||||
|
if (filterDropdownRef.value && !filterDropdownRef.value.contains(event.target)) {
|
||||||
|
isFilterOpen.value = false;
|
||||||
|
}
|
||||||
|
if (exportDropdownRef.value && !exportDropdownRef.value.contains(event.target)) {
|
||||||
|
isExportOpen.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Lifecycle Hooks ---
|
||||||
|
onMounted(() => {
|
||||||
|
fetchRingkasan(pagination.value.current_page);
|
||||||
|
document.addEventListener('click', closeDropdownsOnClickOutside);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('click', closeDropdownsOnClickOutside);
|
||||||
|
});
|
||||||
|
</script>
|
@ -4,7 +4,7 @@
|
|||||||
v-model="searchText"
|
v-model="searchText"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Cari ..."
|
placeholder="Cari ..."
|
||||||
class="border rounded-md px-3 py-2 w-64 focus:outline-none focus:ring-2 focus:ring-blue-400"
|
class="border border-C bg-A rounded-md px-3 py-2 w-64 focus:outline-none focus:ring-2 focus:ring-blue-400"
|
||||||
@input="$emit('update:search', searchText)"
|
@input="$emit('update:search', searchText)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
:product="createdProduct"
|
:product="createdProduct"
|
||||||
@close="closeItemModal"
|
@close="closeItemModal"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<p class="font-serif italic text-[25px] text-D">Produk Baru</p>
|
<p class="font-serif italic text-[25px] text-D">Produk Baru</p>
|
||||||
|
|
||||||
@ -131,6 +130,7 @@ import InputField from "../components/InputField.vue";
|
|||||||
import InputSelect from "../components/InputSelect.vue";
|
import InputSelect from "../components/InputSelect.vue";
|
||||||
import CreateItemModal from "../components/CreateItemModal.vue";
|
import CreateItemModal from "../components/CreateItemModal.vue";
|
||||||
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<mainLayout>
|
<mainLayout>
|
||||||
<div class="lg:p-2 pt-6">
|
<div class="lg:p-2 pt-6">
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-5 gap-3 sm:gap-2 max-w-7xl mx-auto">
|
<div class="grid grid-cols-1 lg:grid-cols-5 gap-3 sm:gap-2 mx-auto min-h-[75vh]">
|
||||||
<!-- Left Section - Form Kasir -->
|
<!-- Left Section - Form Kasir -->
|
||||||
<div class="lg:col-span-3">
|
<div class="lg:col-span-3">
|
||||||
<div class="bg-white rounded-xl shadow-lg border border-B overflow-hidden h-full">
|
<div class="bg-white rounded-xl shadow-lg border border-B overflow-hidden h-full">
|
||||||
|
15
resources/js/pages/Laporan.vue
Normal file
15
resources/js/pages/Laporan.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<mainLayout>
|
||||||
|
<div class="p-6">
|
||||||
|
<p class="font-serif italic text-[25px] text-D">Laporan</p>
|
||||||
|
|
||||||
|
<RingkasanLaporanB />
|
||||||
|
</div>
|
||||||
|
</mainLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import RingkasanLaporanA from '../components/RingkasanLaporanA.vue';
|
||||||
|
import RingkasanLaporanB from '../components/RingkasanLaporanB.vue';
|
||||||
|
import mainLayout from "../layouts/mainLayout.vue";
|
||||||
|
</script>
|
48
resources/js/pages/Login.vue
Normal file
48
resources/js/pages/Login.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex items-center justify-center min-h-screen bg-[#0c4b66]">
|
||||||
|
<div class="bg-white p-8 rounded-2xl shadow-xl w-80 text-center">
|
||||||
|
<!-- Logo + Title -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<img :src="logo" alt="Logo" class="mx-auto w-34 py-5">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Input -->
|
||||||
|
<div>
|
||||||
|
<InputField v-model="username" type="text" placeholder="Username"class="mb-4"/>
|
||||||
|
<PasswordInput v-model="password" placeholder="Password" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Button -->
|
||||||
|
<button
|
||||||
|
@click="handleLogin"
|
||||||
|
class="w-full py-2 bg-sky-400 hover:bg-sky-500 rounded font-bold text-gray-800 transition"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import logo from '@/../images/logo.png'
|
||||||
|
import InputField from "@/components/InputField.vue";
|
||||||
|
const username = ref("");
|
||||||
|
const password = ref("");
|
||||||
|
import PasswordInput from "@/components/InputPassword.vue";
|
||||||
|
// import { ref } from "vue";
|
||||||
|
|
||||||
|
// const username = ref("");
|
||||||
|
// const password = ref("");
|
||||||
|
|
||||||
|
// const handleLogin = () => {
|
||||||
|
// if (!username.value || !password.value) {
|
||||||
|
// alert("Harap isi username dan password!");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// // Contoh: panggil API login
|
||||||
|
// console.log("Login dengan:", username.value, password.value);
|
||||||
|
// };
|
||||||
|
</script>
|
@ -8,14 +8,19 @@ import InputProduk from '../pages/InputProduk.vue'
|
|||||||
import Kategori from '../pages/Kategori.vue'
|
import Kategori from '../pages/Kategori.vue'
|
||||||
import Sales from '../pages/Sales.vue'
|
import Sales from '../pages/Sales.vue'
|
||||||
import EditProduk from '../pages/EditProduk.vue'
|
import EditProduk from '../pages/EditProduk.vue'
|
||||||
|
import Laporan from '../pages/Laporan.vue'
|
||||||
|
|
||||||
|
import Login from '../pages/Login.vue'
|
||||||
|
|
||||||
import Akun from '../pages/Akun.vue'
|
import Akun from '../pages/Akun.vue'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Home',
|
name: 'Login',
|
||||||
component: Home
|
component: Login
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/produk',
|
path: '/produk',
|
||||||
@ -62,6 +67,11 @@ const routes = [
|
|||||||
name: 'EditProduk',
|
name: 'EditProduk',
|
||||||
component: EditProduk,
|
component: EditProduk,
|
||||||
props: true // biar id bisa langsung jadi props di komponen
|
props: true // biar id bisa langsung jadi props di komponen
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/laporan',
|
||||||
|
name: 'EditProduk',
|
||||||
|
component: Laporan
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\AuthController;
|
use App\Http\Controllers\AuthController;
|
||||||
use App\Http\Controllers\FotoSementaraController;
|
use App\Http\Controllers\FotoSementaraController;
|
||||||
use App\Http\Controllers\ItemController;
|
use App\Http\Controllers\ItemController;
|
||||||
@ -21,6 +20,30 @@
|
|||||||
Route::apiResource('transaksi', TransaksiController::class);
|
Route::apiResource('transaksi', TransaksiController::class);
|
||||||
Route::apiResource('kategori', KategoriController::class);
|
Route::apiResource('kategori', KategoriController::class);
|
||||||
|
|
||||||
|
|
||||||
|
// Backend API
|
||||||
|
Route::prefix('api')->group(function () {
|
||||||
|
Route::apiResource('nampan', NampanController::class);
|
||||||
|
Route::apiResource('produk', ProdukController::class);
|
||||||
|
Route::apiResource('item', ItemController::class);
|
||||||
|
Route::apiResource('sales', SalesController::class);
|
||||||
|
Route::apiResource('user', UserController::class);
|
||||||
|
Route::apiResource('transaksi', TransaksiController::class);
|
||||||
|
Route::apiResource('kategori', KategoriController::class);
|
||||||
|
|
||||||
|
Route::get('brankas', [ItemController::class, 'brankasItem']);
|
||||||
|
Route::delete('kosongkan-nampan', [NampanController::class, 'kosongkan']);
|
||||||
|
|
||||||
|
// Foto Sementara
|
||||||
|
Route::post('foto/upload', [FotoSementaraController::class, 'upload']);
|
||||||
|
Route::delete('foto/hapus/{id}', [FotoSementaraController::class, 'hapus']);
|
||||||
|
Route::get('foto/{user_id}', [FotoSementaraController::class, 'getAll']);
|
||||||
|
Route::delete('foto/reset/{user_id}', [FotoSementaraController::class, 'reset']);
|
||||||
|
|
||||||
|
// Laporan
|
||||||
|
Route::get('laporan', [LaporanController::class, 'ringkasan']);
|
||||||
|
});
|
||||||
|
|
||||||
Route::get('brankas', [ItemController::class, 'brankasItem']);
|
Route::get('brankas', [ItemController::class, 'brankasItem']);
|
||||||
Route::delete('kosongkan-nampan', [NampanController::class, 'kosongkan']);
|
Route::delete('kosongkan-nampan', [NampanController::class, 'kosongkan']);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user