Files
ytp-glm/src/lib/components/MonthlyFuelReportContent.svelte

611 lines
12 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
import { onMount } from 'svelte';
export let user = null;
let selectedYear = new Date().getFullYear();
let fuelData = [];
let loading = true;
let error = '';
let expandedMonths = new Set();
let currentYear = new Date().getFullYear();
let currentMonth = new Date().getMonth();
// Turkish month names
const turkishMonths = [
'Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran',
'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'
];
// Generate available years (current year and 3 years back)
let availableYears = [];
for (let i = 0; i < 4; i++) {
availableYears.push(currentYear - i);
}
let initialLoadDone = false;
onMount(async () => {
await loadFuelData();
initialLoadDone = true;
});
// Watch for year changes and reload data
$: if (initialLoadDone && selectedYear) {
loadFuelData();
expandedMonths = new Set();
}
async function loadFuelData() {
if (!user) {
error = 'Kullanıcı bilgisi bulunamadı.';
loading = false;
return;
}
loading = true;
error = '';
try {
const params = new URLSearchParams({ status: 'approved' });
if (user.unit_id) {
params.set('unit_id', user.unit_id);
} else if (user.id) {
params.set('manager_id', user.id);
} else {
throw new Error('Kullanıcı bilgilerinde eksik alan var.');
}
const url = `/api/fuel-slips?${params.toString()}`;
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
const allSlips = data.fuelSlips || [];
// Filter slips for the selected year and sort by date
fuelData = allSlips
.filter(slip => {
const slipDate = new Date(slip.date);
return slipDate.getFullYear() === selectedYear;
})
.sort((a, b) => new Date(b.date) - new Date(a.date));
} else {
error = 'Yakıt verileri yüklenemedi.';
}
} catch (err) {
error = `Bağlantı hatası: ${err.message}`;
} finally {
loading = false;
}
}
function toggleMonth(monthIndex) {
if (expandedMonths.has(monthIndex)) {
expandedMonths.delete(monthIndex);
} else {
expandedMonths.add(monthIndex);
}
expandedMonths = expandedMonths;
}
function getMonthData(monthIndex) {
return fuelData.filter(slip => {
const slipDate = new Date(slip.date);
return slipDate.getMonth() === monthIndex;
});
}
function isMonthInFuture(monthIndex) {
return selectedYear === currentYear && monthIndex > currentMonth;
}
function getFuelTypeLabel(type) {
return type === 'benzin' ? '⛽ Benzin' : '🛢️ Motorin';
}
function getMonthTotal(monthIndex) {
const monthData = getMonthData(monthIndex);
return monthData.reduce((total, slip) => total + (slip.liters || 0), 0);
}
function getYearTotal() {
return fuelData.reduce((total, slip) => total + (slip.liters || 0), 0);
}
function formatDate(dateString) {
return new Date(dateString).toLocaleDateString('tr-TR');
}
function formatLiters(liters) {
return Number(liters).toFixed(1);
}
function getSlipCount(monthIndex) {
return getMonthData(monthIndex).length;
}
function getMonthDataSummary() {
const months = ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'];
return months.map((month, index) => ({
month,
count: getSlipCount(index),
total: getMonthTotal(index)
})).filter(m => m.count > 0);
}
// Reactive statement that rebuilds when fuelData or expandedMonths changes
$: monthlyData = (() => {
const months = [];
// Determine max month to show (don't show future months)
const maxMonth = selectedYear === currentYear ? currentMonth : 11;
// Show months from maxMonth down to January
for (let i = maxMonth; i >= 0; i--) {
const data = getMonthData(i);
const monthData = {
month: i,
data: data,
total: getMonthTotal(i),
count: data.length,
isExpanded: expandedMonths.has(i)
};
months.push(monthData);
}
return months;
})();
</script>
<div class="monthly-fuel-report">
<div class="report-header">
<h1 class="report-title">Aylık Yakıt Dökümü</h1>
<div class="year-selector">
<label for="year-select">Yıl:</label>
<select id="year-select" bind:value={selectedYear} on:change={loadFuelData}>
{#each availableYears as year}
<option value={year}>{year}</option>
{/each}
</select>
</div>
</div>
{#if error}
<div class="error-message">
{error}
</div>
{/if}
{#if loading}
<div class="loading-container">
<div class="spinner"></div>
<p>Yükleniyor...</p>
</div>
{:else if fuelData.length === 0}
<div class="empty-state">
<div class="empty-icon">
<i class="fa-solid fa-chart-line"></i>
</div>
<h3>Veri Bulunamadı</h3>
<p>{selectedYear} yılı için onaylı yakıt fişi bulunamadı.</p>
</div>
{:else}
<!-- Year Summary -->
<div class="year-summary card">
<div class="summary-item">
<span class="summary-label">Yıl Toplamı:</span>
<span class="summary-value">{formatLiters(getYearTotal())} Litre</span>
</div>
<div class="summary-item">
<span class="summary-label">Toplam Fiş:</span>
<span class="summary-value">{fuelData.length} Adet</span>
</div>
</div>
<!-- Monthly Accordion -->
<div class="months-container">
{#each monthlyData as month (month.month)}
<div class="month-card">
<div
class="month-header"
class:has-data={month.count > 0}
class:expanded={month.isExpanded}
on:click={() => toggleMonth(month.month)}
>
<div class="month-info">
<h3 class="month-name">
{turkishMonths[month.month]} {selectedYear}
</h3>
{#if month.count > 0}
<div class="month-stats">
<span class="stat-badge">{month.count} Fiş</span>
<span class="stat-badge">{formatLiters(month.total)}L</span>
</div>
{:else}
<div class="month-stats">
<span class="stat-badge empty">Veri Yok</span>
</div>
{/if}
</div>
<div class="expand-icon">
<i class="fa-solid fa-chevron-{month.isExpanded ? 'up' : 'down'}"></i>
</div>
</div>
{#if month.isExpanded && month.count > 0}
<div class="month-content">
<div class="fuel-table-container">
<table class="fuel-table">
<thead>
<tr>
<th>S.No</th>
<th>Araç Plakası</th>
<th>Tarih</th>
<th>Yakıt Cinsi</th>
<th>Miktar</th>
<th>Teslim Alan</th>
<th>Teslim Eden</th>
</tr>
</thead>
<tbody>
{#each month.data.sort((a, b) => new Date(a.date) - new Date(b.date)) as slip, index}
<tr>
<td>{index + 1}</td>
<td class="plate-cell">{slip.vehicle_info?.plate || '-'}</td>
<td>{formatDate(slip.date)}</td>
<td>{getFuelTypeLabel(slip.fuel_type)}</td>
<td class="quantity-cell">{formatLiters(slip.liters)}</td>
<td>{slip.personnel_info?.rank} {slip.personnel_info?.full_name || '-'}</td>
<td>{slip.fuel_manager_info?.full_name || '-'}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
{/if}
</div>
{/each}
</div>
{/if}
</div>
<style>
.monthly-fuel-report {
padding: 0;
max-width: none;
margin: 0;
}
.report-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
gap: 1rem;
flex-wrap: wrap;
}
.report-title {
font-size: 1.8rem;
font-weight: 700;
color: var(--text-color);
margin: 0;
}
.year-selector {
display: flex;
align-items: center;
gap: 0.75rem;
background: white;
padding: 0.75rem 1rem;
border: 1px solid var(--card-border-color);
border-radius: 8px;
}
.year-selector label {
font-weight: 600;
color: var(--text-color);
}
.year-selector select {
padding: 0.5rem;
border: 1px solid #E5E7EB;
border-radius: 6px;
font-weight: 600;
background: white;
cursor: pointer;
min-width: 100px;
}
.error-message {
background: #FEE2E2;
color: #DC2626;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1.5rem;
border: 1px solid #FECACA;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #E5E7EB;
border-top: 4px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.empty-state {
text-align: center;
padding: 3rem;
background: white;
border-radius: 12px;
border: 1px solid var(--card-border-color);
}
.empty-icon {
font-size: 4rem;
color: var(--text-secondary);
margin-bottom: 1rem;
opacity: 0.5;
}
.empty-state h3 {
font-size: 1.5rem;
font-weight: 600;
color: var(--text-color);
margin-bottom: 0.5rem;
}
.empty-state p {
color: var(--text-secondary);
margin: 0;
}
.year-summary {
display: flex;
gap: 2rem;
padding: 1.5rem;
margin-bottom: 2rem;
background: #E0E7FF;
color: #4338CA;
border: 1px solid #C7D2FE;
}
.summary-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.summary-label {
font-size: 0.9rem;
opacity: 0.9;
font-weight: 500;
}
.summary-value {
font-size: 1.5rem;
font-weight: 700;
}
.months-container {
display: grid;
gap: 1rem;
}
.month-card {
background: white;
border: 1px solid var(--card-border-color);
border-radius: 12px;
overflow: hidden;
transition: all 0.3s ease;
}
.month-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.month-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.25rem 1.5rem;
cursor: pointer;
user-select: none;
transition: all 0.2s ease;
background: #FAFBFC;
}
.month-header:hover {
background: #F3F4F6;
}
.month-header.has-data {
background: linear-gradient(135deg, #F8FAFC 0%, #E2E8F0 100%);
}
.month-header.expanded {
background: var(--primary-color);
color: white;
}
.month-header.expanded .month-name,
.month-header.expanded .stat-badge {
color: white;
}
.month-info {
flex: 1;
}
.month-name {
font-size: 1.2rem;
font-weight: 600;
color: var(--text-color);
margin: 0 0 0.5rem 0;
}
.month-stats {
display: flex;
gap: 0.75rem;
align-items: center;
}
.stat-badge {
background: rgba(59, 130, 246, 0.1);
color: #1E40AF;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
}
.stat-badge.empty {
background: rgba(156, 163, 175, 0.1);
color: #6B7280;
}
.expand-icon {
font-size: 1rem;
transition: transform 0.2s ease;
color: var(--text-secondary);
}
.month-header.expanded .expand-icon {
color: white;
}
.month-content {
border-top: 1px solid var(--card-border-color);
background: white;
}
.fuel-table-container {
overflow-x: auto;
}
.fuel-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.fuel-table th {
background: #F9FAFB;
padding: 1rem;
text-align: left;
font-weight: 600;
color: var(--text-color);
border-bottom: 2px solid #E5E7EB;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.fuel-table td {
padding: 1rem;
border-bottom: 1px solid #F3F4F6;
color: var(--text-color);
vertical-align: middle;
}
.fuel-table tbody tr:hover {
background: #F9FAFB;
}
.plate-cell {
font-weight: 600;
color: var(--primary-color);
}
.quantity-cell {
font-weight: 600;
color: #059669;
}
/* Responsive Design */
@media (max-width: 768px) {
.report-header {
flex-direction: column;
align-items: stretch;
gap: 1rem;
}
.report-title {
font-size: 1.5rem;
text-align: center;
}
.year-selector {
justify-content: center;
}
.year-summary {
flex-direction: column;
gap: 1rem;
text-align: center;
}
.month-header {
padding: 1rem;
}
.month-name {
font-size: 1.1rem;
}
.month-stats {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.fuel-table {
font-size: 0.8rem;
}
.fuel-table th,
.fuel-table td {
padding: 0.75rem 0.5rem;
}
.stat-badge {
font-size: 0.7rem;
padding: 0.2rem 0.6rem;
}
}
/* Animation */
.month-content {
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
max-height: 0;
}
to {
opacity: 1;
max-height: 2000px;
}
}
</style>