refactor: simplify vehicle system to remove fleet management

This commit is contained in:
ypratama committed 2026-08-26 10:42:35 +07:00
1 parent c11cbcdc89
commit 11abe508bf
5 files changed
+10 -185

No files matched your search

-9
View File
@@ -122,17 +122,9 @@ model ActivityLog {
createdAt DateTime @default(now())
}
model Vehicle {
id Int @id @default(autoincrement())
name String @unique
status String @default("available") // "available", "borrowed", "maintenance"
requests VehicleRequest[]
}
model VehicleRequest {
id Int @id @default(autoincrement())
userId Int
vehicleId Int
borrowerName String
agenda String
borrowTime String
@@ -140,7 +132,6 @@ model VehicleRequest {
status String @default("pending") // "pending", "approved", "rejected", "returned"
user User @relation(fields: [userId], references: [id])
vehicle Vehicle @relation(fields: [vehicleId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
-138
View File
@@ -1,138 +0,0 @@
import { prisma } from '@/lib/prisma'
import { getSession } from '@/lib/auth'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export default async function AdminVehiclesPage({ searchParams }: { searchParams: Promise<{ edit?: string }> }) {
const session = await getSession()
if (!session || session.role !== 'super_admin') {
redirect('/')
}
const sp = await searchParams
const vehicles = await prisma.vehicle.findMany({
orderBy: { id: 'desc' }
})
let editingVehicle = null
if (sp.edit) {
editingVehicle = vehicles.find((v: any) => v.id === parseInt(sp.edit!))
}
async function addVehicle(formData: FormData) {
'use server'
const name = formData.get('name') as string
if (name) {
await prisma.vehicle.create({ data: { name } })
}
revalidatePath('/admin/vehicles')
redirect('/admin/vehicles')
}
async function updateVehicle(formData: FormData) {
'use server'
const id = parseInt(formData.get('id') as string)
const name = formData.get('name') as string
const status = formData.get('status') as string
if (id && name) {
await prisma.vehicle.update({ where: { id }, data: { name, status } })
}
revalidatePath('/admin/vehicles')
redirect('/admin/vehicles')
}
async function deleteVehicle(formData: FormData) {
'use server'
const id = parseInt(formData.get('id') as string)
if (id) {
try {
await prisma.vehicle.delete({ where: { id } })
} catch (e) {
console.error("Error deleting vehicle:", e)
}
}
revalidatePath('/admin/vehicles')
redirect('/admin/vehicles')
}
return (
<main style={{ padding: '2rem', maxWidth: '1200px', margin: '0 auto' }}>
<h1 className="heading" style={{ marginBottom: '2rem' }}>Vehicle Fleet Management</h1>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 300px', gap: '2rem' }}>
<div className="glass-panel" style={{ padding: '1.5rem' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', textAlign: 'left' }}>
<thead>
<tr>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Vehicle Name / Plate</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Status</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Actions</th>
</tr>
</thead>
<tbody>
{vehicles.map((veh: any) => (
<tr key={veh.id}>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>{veh.name}</td>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>
<span style={{
padding: '4px 8px', borderRadius: '4px', fontSize: '0.8rem',
background: veh.status === 'available' ? 'rgba(16, 185, 129, 0.2)' : 'rgba(239, 68, 68, 0.2)',
color: veh.status === 'available' ? 'var(--success)' : 'var(--danger)'
}}>
{veh.status.toUpperCase()}
</span>
</td>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)', display: 'flex', gap: '0.5rem' }}>
<a href={`/admin/vehicles?edit=${veh.id}`} className="btn-secondary" style={{ padding: '0.5rem 1rem', textDecoration: 'none' }}>Edit</a>
<form action={deleteVehicle}>
<input type="hidden" name="id" value={veh.id} />
<button type="submit" className="btn-secondary" style={{ padding: '0.5rem 1rem', background: 'rgba(239, 68, 68, 0.1)', color: 'var(--danger)', border: '1px solid rgba(239, 68, 68, 0.2)' }}>Delete</button>
</form>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="glass-panel" style={{ padding: '1.5rem', height: 'fit-content' }}>
{editingVehicle ? (
<>
<h2 className="heading" style={{ fontSize: '1.25rem', marginBottom: '1rem' }}>Edit Vehicle</h2>
<form action={updateVehicle} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<input type="hidden" name="id" value={editingVehicle.id} />
<div>
<label className="label">Vehicle Name / Plate</label>
<input name="name" type="text" className="input-field" defaultValue={editingVehicle.name} required />
</div>
<div>
<label className="label">Status</label>
<select name="status" className="input-field" defaultValue={editingVehicle.status}>
<option value="available">Available</option>
<option value="borrowed">Borrowed</option>
<option value="maintenance">Maintenance</option>
</select>
</div>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<button type="submit" className="btn-primary" style={{ flex: 1, background: 'var(--accent)' }}>Update</button>
<a href="/admin/vehicles" className="btn-secondary" style={{ padding: '0.75rem 1.5rem', textDecoration: 'none' }}>Cancel</a>
</div>
</form>
</>
) : (
<>
<h2 className="heading" style={{ fontSize: '1.25rem', marginBottom: '1rem' }}>Add New Vehicle</h2>
<form action={addVehicle} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<div>
<label className="label">Vehicle Name / Plate</label>
<input name="name" type="text" className="input-field" placeholder="e.g. Innova B 1234 CD" required />
</div>
<button type="submit" className="btn-primary" style={{ marginTop: '0.5rem' }}>Add Vehicle</button>
</form>
</>
)}
</div>
</div>
</main>
)
}
+7 -16
View File
@@ -23,19 +23,19 @@ export default async function VehicleApprovalsPage() {
const pendingRequests = await prisma.vehicleRequest.findMany({
where: { status: 'pending' },
include: { vehicle: true, user: true },
include: { user: true },
orderBy: { requestDate: 'asc' }
})
const approvedRequests = await prisma.vehicleRequest.findMany({
where: { status: 'approved' },
include: { vehicle: true, user: true },
include: { user: true },
orderBy: { requestDate: 'asc' }
})
const history = await prisma.vehicleRequest.findMany({
where: { status: { in: ['rejected', 'returned'] } },
include: { vehicle: true, user: true },
include: { user: true },
orderBy: { requestDate: 'desc' },
take: 50
})
@@ -44,16 +44,13 @@ export default async function VehicleApprovalsPage() {
'use server'
const id = parseInt(formData.get('id') as string)
const action = formData.get('action') as string
const vehicleId = parseInt(formData.get('vehicleId') as string)
if (action === 'approve') {
await prisma.vehicleRequest.update({ where: { id }, data: { status: 'approved' } })
await prisma.vehicle.update({ where: { id: vehicleId }, data: { status: 'borrowed' } })
} else if (action === 'reject') {
await prisma.vehicleRequest.update({ where: { id }, data: { status: 'rejected' } })
} else if (action === 'return') {
await prisma.vehicleRequest.update({ where: { id }, data: { status: 'returned' } })
await prisma.vehicle.update({ where: { id: vehicleId }, data: { status: 'available' } })
}
revalidatePath('/vehicles/approvals')
@@ -69,7 +66,6 @@ export default async function VehicleApprovalsPage() {
<table style={{ width: '100%', borderCollapse: 'collapse', textAlign: 'left' }}>
<thead>
<tr>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Kendaraan</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Tanggal & Jam</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Peminjam / Agenda</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Actions</th>
@@ -77,10 +73,9 @@ export default async function VehicleApprovalsPage() {
</thead>
<tbody>
{pendingRequests.length === 0 ? (
<tr><td colSpan={4} style={{ padding: '1rem', textAlign: 'center', color: 'var(--text-muted)' }}>No pending requests.</td></tr>
<tr><td colSpan={3} style={{ padding: '1rem', textAlign: 'center', color: 'var(--text-muted)' }}>No pending requests.</td></tr>
) : pendingRequests.map((req: any) => (
<tr key={req.id}>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}><strong>{req.vehicle.name}</strong></td>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>
{new Date(req.requestDate).toLocaleDateString('id-ID')} <br/>
<span style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>Jam: {req.borrowTime}</span>
@@ -95,7 +90,6 @@ export default async function VehicleApprovalsPage() {
) : (
<form action={processRequest} style={{ display: 'flex', gap: '0.5rem' }}>
<input type="hidden" name="id" value={req.id} />
<input type="hidden" name="vehicleId" value={req.vehicleId} />
<button type="submit" name="action" value="approve" className="btn-primary" style={{ padding: '0.4rem 0.8rem', fontSize: '0.8rem' }}>Approve</button>
<button type="submit" name="action" value="reject" className="btn-secondary" style={{ padding: '0.4rem 0.8rem', fontSize: '0.8rem', background: 'rgba(239, 68, 68, 0.1)', color: 'var(--danger)', border: '1px solid rgba(239,68,68,0.2)' }}>Reject</button>
</form>
@@ -112,7 +106,6 @@ export default async function VehicleApprovalsPage() {
<table style={{ width: '100%', borderCollapse: 'collapse', textAlign: 'left' }}>
<thead>
<tr>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Kendaraan</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Tanggal & Jam</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Peminjam / Agenda</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Actions</th>
@@ -120,10 +113,9 @@ export default async function VehicleApprovalsPage() {
</thead>
<tbody>
{approvedRequests.length === 0 ? (
<tr><td colSpan={4} style={{ padding: '1rem', textAlign: 'center', color: 'var(--text-muted)' }}>No active borrowed vehicles.</td></tr>
<tr><td colSpan={3} style={{ padding: '1rem', textAlign: 'center', color: 'var(--text-muted)' }}>No active borrowed vehicles.</td></tr>
) : approvedRequests.map((req: any) => (
<tr key={req.id}>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}><strong>{req.vehicle.name}</strong></td>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>
{new Date(req.requestDate).toLocaleDateString('id-ID')} <br/>
<span style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>Jam: {req.borrowTime}</span>
@@ -138,7 +130,6 @@ export default async function VehicleApprovalsPage() {
) : (
<form action={processRequest}>
<input type="hidden" name="id" value={req.id} />
<input type="hidden" name="vehicleId" value={req.vehicleId} />
<button type="submit" name="action" value="return" className="btn-primary" style={{ padding: '0.4rem 0.8rem', fontSize: '0.8rem', background: '#3b82f6' }}>Mark as Returned</button>
</form>
)}
@@ -154,7 +145,7 @@ export default async function VehicleApprovalsPage() {
<table style={{ width: '100%', borderCollapse: 'collapse', textAlign: 'left' }}>
<thead>
<tr>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Kendaraan</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Tanggal</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Peminjam</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Agenda</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Status</th>
@@ -165,7 +156,7 @@ export default async function VehicleApprovalsPage() {
<tr><td colSpan={4} style={{ padding: '1rem', textAlign: 'center', color: 'var(--text-muted)' }}>No history.</td></tr>
) : history.map((req: any) => (
<tr key={req.id}>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}><strong>{req.vehicle.name}</strong></td>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>{new Date(req.requestDate).toLocaleDateString('id-ID')}</td>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>{req.borrowerName}</td>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>{req.agenda}</td>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>
+3 -21
View File
@@ -9,25 +9,19 @@ export default async function VehicleRequestPage({ searchParams }: { searchParam
redirect('/login')
}
const sp = await searchParams
const vehicles = await prisma.vehicle.findMany({
where: { status: 'available' }
})
const history = await prisma.vehicleRequest.findMany({
where: { userId: session.id },
include: { vehicle: true },
orderBy: { createdAt: 'desc' }
})
async function submitVehicleRequest(formData: FormData) {
'use server'
const vehicleIdStr = formData.get('vehicleId') as string
const borrowerName = formData.get('borrowerName') as string
const agenda = formData.get('agenda') as string
const borrowTime = formData.get('borrowTime') as string
if (!vehicleIdStr || !borrowerName || !agenda || !borrowTime) {
if (!borrowerName || !agenda || !borrowTime) {
redirect('/vehicles/request?error=Missing+fields')
}
@@ -37,7 +31,6 @@ export default async function VehicleRequestPage({ searchParams }: { searchParam
await prisma.vehicleRequest.create({
data: {
userId: currentSession.id,
vehicleId: parseInt(vehicleIdStr),
borrowerName,
agenda,
borrowTime
@@ -57,15 +50,6 @@ export default async function VehicleRequestPage({ searchParams }: { searchParam
<div className="glass-panel" style={{ padding: '1.5rem', height: 'fit-content' }}>
<h2 className="heading" style={{ fontSize: '1.25rem', marginBottom: '1rem' }}>Form Peminjaman</h2>
<form action={submitVehicleRequest} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<div>
<label className="label">Pilih Kendaraan</label>
<select name="vehicleId" className="input-field" required>
<option value="">-- Pilih Mobil Tersedia --</option>
{vehicles.map((v: any) => (
<option key={v.id} value={v.id}>{v.name}</option>
))}
</select>
</div>
<div>
<label className="label">Nama Peminjam</label>
<input type="text" name="borrowerName" className="input-field" placeholder="Masukkan nama peminjam" required />
@@ -79,7 +63,7 @@ export default async function VehicleRequestPage({ searchParams }: { searchParam
<input type="time" name="borrowTime" className="input-field" required />
</div>
<p style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>
*Tanggal peminjaman akan otomatis tercatat berdasarkan waktu form ini dikirimkan.
*Tanggal peminjaman otomatis tercatat dari sistem saat ini.
</p>
<button type="submit" className="btn-primary" style={{ marginTop: '0.5rem' }}>Submit Request</button>
</form>
@@ -90,7 +74,6 @@ export default async function VehicleRequestPage({ searchParams }: { searchParam
<table style={{ width: '100%', borderCollapse: 'collapse', textAlign: 'left' }}>
<thead>
<tr>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Kendaraan</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Tanggal & Jam</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Peminjam / Agenda</th>
<th style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>Status</th>
@@ -99,11 +82,10 @@ export default async function VehicleRequestPage({ searchParams }: { searchParam
<tbody>
{history.length === 0 ? (
<tr>
<td colSpan={4} style={{ padding: '1rem', textAlign: 'center', color: 'var(--text-muted)' }}>Belum ada riwayat peminjaman.</td>
<td colSpan={3} style={{ padding: '1rem', textAlign: 'center', color: 'var(--text-muted)' }}>Belum ada riwayat peminjaman.</td>
</tr>
) : history.map((req: any) => (
<tr key={req.id}>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}><strong>{req.vehicle.name}</strong></td>
<td style={{ padding: '1rem', borderBottom: '1px solid var(--glass-border)' }}>
{new Date(req.requestDate).toLocaleDateString('id-ID')} <br/>
<span style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>Jam: {req.borrowTime}</span>
-1
View File
@@ -41,7 +41,6 @@ export default function Navbar({ role, name, pendingCount = 0, isSales = false }
)}
{role === 'super_admin' && (
<>
<Link href="/admin/vehicles">Armada Mobil</Link>
<Link href="/admin/divisions">Divisions</Link>
<Link href="/admin/categories">Categories</Link>
<Link href="/admin/users">Users</Link>