refactor: simplify vehicle system to remove fleet management
This commit is contained in:
1 parent
c11cbcdc89
commit
11abe508bf
5 files changed
+10
-185
No files matched your search
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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)' }}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user