205 lines
9.7 KiB
PHP
205 lines
9.7 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', 'Inventory Items')
|
|
|
|
@section('content')
|
|
<div class="page-header">
|
|
<div>
|
|
<h1 class="page-title">Inventory Items</h1>
|
|
<p class="page-subtitle">Manage all stock items</p>
|
|
</div>
|
|
<div class="flex items-center gap-2 flex-wrap">
|
|
<a href="{{ route('inventory.items.export-pdf') }}" class="btn btn-secondary" title="Export items as PDF">
|
|
<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6M9 17h4"/>
|
|
</svg>
|
|
Export PDF
|
|
</a>
|
|
<a href="{{ route('inventory.items.template') }}" class="btn btn-secondary" title="Download Excel import template">
|
|
<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
|
</svg>
|
|
Template
|
|
</a>
|
|
<button onclick="document.getElementById('import-modal').classList.remove('hidden')"
|
|
class="btn btn-success" title="Import items from Excel">
|
|
<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
|
|
</svg>
|
|
Import Excel
|
|
</button>
|
|
<a href="{{ route('inventory.items.create') }}" class="btn-primary">
|
|
+ Add Item
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-wrapper overflow-x-auto">
|
|
<table class="table-base">
|
|
<thead>
|
|
<tr>
|
|
<th>Code</th>
|
|
<th>Name</th>
|
|
<th>Category</th>
|
|
<th>UOM</th>
|
|
<th class="text-right">Min Stock</th>
|
|
<th class="text-right">Cost Price</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($items as $item)
|
|
<tr>
|
|
<td class="font-mono text-gray-700">{{ $item->item_code }}</td>
|
|
<td class="font-medium text-gray-800">{{ $item->item_name }}</td>
|
|
<td>
|
|
@php
|
|
$catBadgeClass = match($item->category) {
|
|
'raw_material' => 'badge-blue',
|
|
'wip' => 'badge-yellow',
|
|
'finished_good' => 'badge-green',
|
|
default => 'badge-gray',
|
|
};
|
|
$catLabels = ['raw_material' => 'Raw Material', 'wip' => 'WIP', 'finished_good' => 'Finished Good'];
|
|
@endphp
|
|
<span class="{{ $catBadgeClass }}">
|
|
{{ $catLabels[$item->category] ?? ucfirst($item->category) }}
|
|
</span>
|
|
</td>
|
|
<td>{{ $item->unit_of_measure }}</td>
|
|
<td class="text-right">{{ number_format($item->minimum_stock_level, 2) }}</td>
|
|
<td class="text-right text-gray-800">{{ number_format($item->cost_price, 2) }}</td>
|
|
<td>
|
|
@if($item->is_active)
|
|
<span class="badge-green">Active</span>
|
|
@else
|
|
<span class="badge-gray">Inactive</span>
|
|
@endif
|
|
</td>
|
|
<td>
|
|
<div class="flex items-center gap-2">
|
|
<a href="{{ route('inventory.items.edit', $item) }}" class="btn-secondary btn-sm">Edit</a>
|
|
<form action="{{ route('inventory.items.destroy', $item) }}" method="POST"
|
|
onsubmit="confirmDelete(this,'Delete this item?','This inventory item will be permanently removed.'); return false;">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" class="btn-danger btn-sm">Delete</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="8" class="px-4 py-8 text-center text-gray-400">No items found.</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
@if($items->hasPages())
|
|
<div class="mt-4">{{ $items->links() }}</div>
|
|
@endif
|
|
|
|
{{-- ═══════════ Import Modal ═══════════ --}}
|
|
<div id="import-modal"
|
|
class="hidden fixed inset-0 z-50 flex items-center justify-center"
|
|
style="background:rgba(0,0,0,.45);">
|
|
<div class="bg-white rounded-2xl shadow-xl w-full max-w-md mx-4" style="animation:fadeInUp .2s ease;">
|
|
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
|
<h2 class="text-base font-semibold text-slate-800">Import Items from Excel</h2>
|
|
<button onclick="document.getElementById('import-modal').classList.add('hidden')"
|
|
class="text-slate-400 hover:text-slate-600 transition-colors">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<form action="{{ route('inventory.items.import') }}" method="POST" enctype="multipart/form-data" class="p-6">
|
|
@csrf
|
|
|
|
<label for="import-file"
|
|
id="drop-zone"
|
|
class="flex flex-col items-center justify-center gap-3 w-full border-2 border-dashed border-slate-300 rounded-xl p-8 cursor-pointer transition-colors hover:border-blue-400 hover:bg-blue-50"
|
|
ondragover="event.preventDefault(); this.classList.add('border-blue-500','bg-blue-50')"
|
|
ondragleave="this.classList.remove('border-blue-500','bg-blue-50')"
|
|
ondrop="itemHandleDrop(event)">
|
|
<svg width="36" height="36" fill="none" stroke="#94a3b8" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
|
d="M9 13h6M12 10v6m-7 4h14a2 2 0 002-2V7a2 2 0 00-2-2h-5.586a1 1 0 01-.707-.293l-1.414-1.414A1 1 0 0010.586 3H5a2 2 0 00-2 2v14a2 2 0 002 2z"/>
|
|
</svg>
|
|
<div class="text-center">
|
|
<p id="item-drop-label" class="text-sm font-medium text-slate-700">
|
|
Click to choose or drag & drop your file
|
|
</p>
|
|
<p class="text-xs text-slate-400 mt-1">Accepts: .xlsx, .xls — max 10 MB</p>
|
|
</div>
|
|
<input id="import-file" type="file" name="file" accept=".xlsx,.xls"
|
|
class="sr-only" onchange="itemUpdateLabel(this)">
|
|
</label>
|
|
|
|
<div class="mt-4 p-3 rounded-lg bg-amber-50 border border-amber-200 text-xs text-amber-700 flex gap-2">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24" class="flex-shrink-0 mt-0.5">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
<span>
|
|
Supports the <strong>Forkoll inventory format</strong> and the
|
|
<strong>standard item template</strong>. Categories are auto-detected.
|
|
Items marked "Not in Use" are imported as inactive. Duplicate names are skipped.
|
|
</span>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-end gap-3 mt-5">
|
|
<button type="button"
|
|
onclick="document.getElementById('import-modal').classList.add('hidden')"
|
|
class="btn-secondary">
|
|
Cancel
|
|
</button>
|
|
<button type="submit" class="btn-primary">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
|
|
</svg>
|
|
Import
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
@keyframes fadeInUp {
|
|
from { opacity:0; transform:translateY(12px); }
|
|
to { opacity:1; transform:translateY(0); }
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
function itemUpdateLabel(input) {
|
|
document.getElementById('item-drop-label').textContent =
|
|
input.files.length ? input.files[0].name : 'Click to choose or drag & drop your file';
|
|
}
|
|
function itemHandleDrop(e) {
|
|
e.preventDefault();
|
|
document.getElementById('drop-zone').classList.remove('border-blue-500', 'bg-blue-50');
|
|
const file = e.dataTransfer.files[0];
|
|
if (!file) return;
|
|
const input = document.getElementById('import-file');
|
|
const dt = new DataTransfer();
|
|
dt.items.add(file);
|
|
input.files = dt.files;
|
|
itemUpdateLabel(input);
|
|
}
|
|
document.getElementById('import-modal').addEventListener('click', function(e) {
|
|
if (e.target === this) this.classList.add('hidden');
|
|
});
|
|
</script>
|
|
@endsection
|