feat: initialize new project with Kanban dashboard and ledger

This commit is contained in:
Vivek 2026-06-09 22:29:32 +00:00
commit 1c5c195abc
26 changed files with 820 additions and 0 deletions

2
.env Normal file
View file

@ -0,0 +1,2 @@
VITE_SUPABASE_URL=http://supabasekong-fih38liv55125sbp2w17z2da.187.127.178.110.sslip.io
VITE_SUPABASE_ANON_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTc4MDg1ODgwMCwiZXhwIjo0OTM2NTMyNDAwLCJyb2xlIjoiYW5vbiJ9.zAIGJwSek9J4_GCYKTRqquFeiajBDAzChWBlidP3Ayk

35
Dockerfile Normal file
View file

@ -0,0 +1,35 @@
# Build stage
FROM node:18-alpine AS builder
# Install git (needed by some npm postinstall scripts)
RUN apk add --no-cache git
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy source files
COPY . .
# Build the Vite production bundle
ENV NODE_ENV=production
RUN npm run build
# Production stage - tiny static server
FROM nginx:alpine
# Copy custom config (removes default, sets SPA fallback)
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built assets
COPY --from=builder /app/dist /usr/share/nginx/html
# Expose port (Coolify expects 80 by default)
EXPOSE 80
# Start nginx in foreground
CMD ["nginx", "-g", "daemon off;"]

53
README-COOLIFY.md Normal file
View file

@ -0,0 +1,53 @@
# Coolify Deployment Guide for Internal Freight App
## Prerequisites
- Supabase project URL & anon key (set in `.env`)
- Node.js >= 18 (handled via Docker build)
- Your VPS has Docker + Coolify installed
## File Structure
```
internal-freight-app/
├── Dockerfile # Docker build for Coolify
├── .dockerignore # Ignore unwanted files
├── .env.example # Environment variables template
├── nginx.conf # Nginx reverse proxy config (default Coolify)
├── vite.config.ts # Vite config (for asset handling)
├── src/
│ └── ... # React + TypeScript source
└── package.json
```
## Deployment Steps on Coolify
1. **Create Application in Coolify**
- Source: **Git Repository** → URL = `http://forgejo-vil3xyowqk0qsh4hiqy77e3h.187.127.178.110.sslip.io/iamcoolvivek007/internal-freight-app.git`
- Build Pack: **Dockerfile**
- Port: `5173` (Vite dev) or `3000` (if using a different server)
2. **Environment Variables**
- In **Coolify > Application > Environment**, set these:
- `VITE_SUPABASE_URL` = your Supabase project URL (e.g., `https://xyz.supabase.co`)
- `VITE_SUPABASE_ANON_KEY` = your Supabase anon/public key
3. **Deploy**
- Click **Save & Deploy**. Coolify will push every commit to main automatically (or via webhook).
4. **Database**
- Apply the Supabase schema from `supabase-schema.sql` in Supabase SQL Editor:
```sql
-- (schema content)
```
## Local Development (Optional)
```bash
git clone <repo-url>
cp .env.example .env
# Set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY
npm install
npm run dev
```
## Notes
- All data stored in Supabase (no local database required).
- For production, swap `npm run dev` with `npm run preview` or add a Nginx server in Coolify (Dockerfile handles this automatically).

9
README.md Normal file
View file

@ -0,0 +1,9 @@
# New Project
A new freight forwarding project.
## Setup
1. Clone the repo
2. Install dependencies
3. Run the project

12
index.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Freight Internal App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

13
nginx.conf Normal file
View file

@ -0,0 +1,13 @@
server {
listen 80 default_server;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html;
}
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml+rss;
}

27
package.json Normal file
View file

@ -0,0 +1,27 @@
{
"name": "internal-freight-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@supabase/supabase-js": "^2.39.0",
"@tanstack/react-query": "^5.28.0",
"@tanstack/react-table": "^8.15.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.24",
"@types/react-dom": "^18.2.8",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"tailwindcss": "^3.3.2",
"vite": "^5.2.0",
"typescript": "^5.4.5"
}
}

6
postcss.config.js Normal file
View file

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

13
public/index.html Normal file
View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FreightDesk Internal</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

36
src/App.jsx Normal file
View file

@ -0,0 +1,36 @@
import React from 'react'
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'
import Dashboard from './components/Dashboard.jsx'
import Kanban from './components/Kanban.jsx'
import Shippers from './components/Shippers.jsx'
import Drivers from './components/Drivers.jsx'
function App() {
return (
<Router>
<div className="min-h-screen bg-gray-50">
<header className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4 py-4">
<h1 className="text-2xl font-bold text-gray-900">FreightDesk Internal</h1>
<nav className="mt-2 flex space-x-4">
<Link to="/" className="text-blue-600 hover:underline">Dashboard</Link>
<Link to="/kanban" className="text-blue-600 hover:underline">Loads Board</Link>
<Link to="/shippers" className="text-blue-600 hover:underline">Shippers</Link>
<Link to="/drivers" className="text-blue-600 hover:underline">Drivers</Link>
</nav>
</div>
</header>
<main className="max-w-7xl mx-auto p-4">
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/kanban" element={<Kanban />} />
<Route path="/shippers" element={<Shippers />} />
<Route path="/drivers" element={<Drivers />} />
</Routes>
</main>
</div>
</Router>
)
}
export default App

10
src/App.tsx Normal file
View file

@ -0,0 +1,10 @@
import LoadTable from './components/LoadTable';
export default function App() {
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">Freight Desk - Internal App</h1>
<LoadTable />
</div>
);
}

View file

@ -0,0 +1,92 @@
import React, { useState, useEffect } from 'react'
import { supabase } from '../services/supabase.js'
function Dashboard() {
const [stats, setStats] = useState({
totalLoads: 0,
pendingLoads: 0,
totalCommission: 0,
pendingFromShipper: 0,
pendingToDriver: 0
})
useEffect(() => {
// Fetch stats from Supabase
const fetchStats = async () => {
try {
// Total loads
const { count: totalLoads } = await supabase
.from('loads')
.select('*', { count: 'exact', head: true })
// Pending loads (not settled)
const { count: pendingLoads } = await supabase
.from('loads')
.select('*', { count: 'exact', head: true })
.neq('status', 'settled')
// Sum of commission
const { data: commissionData } = await supabase
.from('loads')
.select('commission')
const totalCommission = commissionData?.reduce((sum, l) => sum + (parseFloat(l.commission) || 0), 0) || 0
// Sum of pending_from_shipper
const { data: pendingShipperData } = await supabase
.from('loads')
.select('pending_from_shipper')
const pendingFromShipper = pendingShipperData?.reduce((sum, l) => sum + (parseFloat(l.pending_from_shipper) || 0), 0) || 0
// Sum of pending_to_driver
const { data: pendingDriverData } = await supabase
.from('loads')
.select('pending_to_driver')
const pendingToDriver = pendingDriverData?.reduce((sum, l) => sum + (parseFloat(l.pending_to_driver) || 0), 0) || 0
setStats({
totalLoads: totalLoads || 0,
pendingLoads: pendingLoads || 0,
totalCommission,
pendingFromShipper,
pendingToDriver
})
} catch (error) {
console.error('Error fetching stats:', error)
}
}
fetchStats()
}, [])
return (
<div className="space-y-6">
<h2 className="text-xl font-semibold">Dashboard</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
<StatCard title="Total Loads" value={stats.totalLoads} icon="📦" />
<StatCard title="Pending Loads" value={stats.pendingLoads} icon="⏳" />
<StatCard title="Total Commission" value={`${stats.totalCommission.toLocaleString()}`} icon="💰" />
<StatCard title="Pending from Shipper" value={`${stats.pendingFromShipper.toLocaleString()}`} icon="📥" />
<StatCard title="Pending to Driver" value={`${stats.pendingToDriver.toLocaleString()}`} icon="📤" />
</div>
</div>
)
}
function StatCard({ title, value, icon }) {
return (
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-500">{title}</p>
<p className="text-2xl font-bold text-gray-900 mt-1">{value}</p>
</div>
<span className="text-3xl">{icon}</span>
</div>
</div>
)
}
export default Dashboard

View file

@ -0,0 +1,58 @@
import React, { useState, useEffect } from 'react'
import { supabase } from '../services/supabase.js'
function Drivers() {
const [drivers, setDrivers] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchDrivers = async () => {
try {
const { data, error } = await supabase
.from('drivers')
.select('*')
.order('created_at', { ascending: false })
if (error) throw error
setDrivers(data || [])
} catch (error) {
console.error('Error fetching drivers:', error)
} finally {
setLoading(false)
}
}
fetchDrivers()
}, [])
if (loading) return <div className="text-center py-8">Loading drivers...</div>
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Drivers</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{drivers.length > 0 ? (
drivers.map(driver => (
<DriverCard key={driver.id} driver={driver} />
))
) : (
<p className="text-gray-500 col-span-full">No drivers found. Add your first driver!</p>
)}
</div>
</div>
)
}
function DriverCard({ driver }) {
return (
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-blue-500">
<div className="font-semibold">{driver.name}</div>
<div className="text-sm text-gray-600">Vehicle: {driver.vehicle_number || 'N/A'}</div>
<div className="text-sm text-gray-600">Contact: {driver.contact || 'N/A'}</div>
<div className="text-sm text-gray-600">Total Earnings: {driver.total_earnings?.toLocaleString() || '0'}</div>
<div className="text-sm text-gray-600">Pending Payments: {driver.pending_payments?.toLocaleString() || '0'}</div>
</div>
)
}
export default Drivers

116
src/components/Kanban.jsx Normal file
View file

@ -0,0 +1,116 @@
import React, { useState, useEffect } from 'react'
import { supabase } from '../services/supabase.js'
const STATUS_COLUMNS = [
{ id: 'pending', title: 'Pending', color: 'bg-gray-200' },
{ id: 'assigned', title: 'Assigned', color: 'bg-blue-200' },
{ id: 'loaded', title: 'Loaded / In Transit', color: 'bg-yellow-200' },
{ id: 'delivered', title: 'Delivered', color: 'bg-green-200' },
{ id: 'settled', title: 'Settled', color: 'bg-purple-200' }
]
function Kanban() {
const [loads, setLoads] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchLoads = async () => {
try {
const { data, error } = await supabase
.from('loads')
.select('*')
.order('date', { ascending: false })
if (error) throw error
setLoads(data || [])
} catch (error) {
console.error('Error fetching loads:', error)
} finally {
setLoading(false)
}
}
fetchLoads()
// Subscribe to real-time updates
const subscription = supabase
.from('loads')
.on('*', (payload) => {
setLoads(prev => {
const updated = prev.filter(l => l.id !== payload.new.id)
return [...updated, payload.new]
})
})
.subscribe()
return () => {
subscription.unsubscribe()
}
}, [])
const handleStatusChange = async (loadId, newStatus) => {
try {
const { error } = await supabase
.from('loads')
.update({ status: newStatus })
.eq('id', loadId)
if (error) throw error
} catch (error) {
console.error('Error updating status:', error)
}
}
if (loading) return <div className="text-center py-8">Loading loads...</div>
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Loads Board</h2>
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
{STATUS_COLUMNS.map((column) => (
<div key={column.id} className={`rounded-lg p-4 ${column.color}`}>
<h3 className="font-semibold mb-3">{column.title}</h3>
<div className="space-y-2">
{loads
.filter(load =>
column.id === 'pending'
? load.status === 'pending lead' || load.status === 'pending collection'
: column.id === 'assigned'
? load.status === 'assigned vehicle'
: column.id === 'loaded'
? load.status === 'loaded / in transit'
: column.id === 'delivered'
? load.status === 'delivered'
: load.status === 'settled'
)
.map(load => (
<LoadCard
key={load.id}
load={load}
onStatusChange={handleStatusChange}
/>
))
}
</div>
</div>
))}
</div>
</div>
)
}
function LoadCard({ load, onStatusChange }) {
return (
<div className="bg-white rounded-lg shadow p-3 cursor-move">
<div className="font-medium text-sm">{load.shipper || load.id}</div>
<div className="text-xs text-gray-500">
{load.from} {load.to}
</div>
<div className="text-xs text-gray-700 mt-1">
{parseFloat(load.freight_charged || 0).toLocaleString()}
</div>
</div>
)
}
export default Kanban

View file

View file

@ -0,0 +1,10 @@
import React from 'react';
export default function LoadTable() {
return (
<div className="p-6 border rounded-md shadow-sm">
<h2 className="text-lg font-semibold mb-4">Freight Loads</h2>
<p className="text-gray-600">Data will load from Supabase</p>
</div>
);
}

102
src/components/Payments.jsx Normal file
View file

@ -0,0 +1,102 @@
import React, { useState, useEffect } from 'react'
import { supabase } from '../services/supabase.js'
function Payments() {
const [payments, setPayments] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchPayments = async () => {
try {
const { data, error } = await supabase
.from('loads')
.select('*')
.neq('status', 'pending lead')
.neq('status', 'available vehicle')
.order('date', { ascending: false })
if (error) throw error
setPayments(data || [])
} catch (error) {
console.error('Error fetching payments:', error)
} finally {
setLoading(false)
}
}
fetchPayments()
}, [])
const totalAdvance = payments.reduce((sum, p) => sum + (parseFloat(p.advance_received) || 0), 0)
const totalPaidToDriver = payments.reduce((sum, p) => sum + (parseFloat(p.paid_to_driver) || 0), 0)
const totalPendingShipper = payments.reduce((sum, p) => sum + (parseFloat(p.pending_from_shipper) || 0), 0)
const totalPendingDriver = payments.reduce((sum, p) => sum + (parseFloat(p.pending_to_driver) || 0), 0)
if (loading) return <div className="text-center py-8">Loading payments...</div>
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Payments</h2>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-white rounded-lg shadow p-4">
<div className="text-sm text-gray-500">Total Advance Received</div>
<div className="text-2xl font-bold text-green-600">{totalAdvance.toLocaleString()}</div>
</div>
<div className="bg-white rounded-lg shadow p-4">
<div className="text-sm text-gray-500">Paid to Drivers</div>
<div className="text-2xl font-bold text-red-600">{totalPaidToDriver.toLocaleString()}</div>
</div>
<div className="bg-white rounded-lg shadow p-4">
<div className="text-sm text-gray-500">Pending from Shipper</div>
<div className="text-2xl font-bold text-yellow-600">{totalPendingShipper.toLocaleString()}</div>
</div>
<div className="bg-white rounded-lg shadow p-4">
<div className="text-sm text-gray-500">Pending to Driver</div>
<div className="text-2xl font-bold text-purple-600">{totalPendingDriver.toLocaleString()}</div>
</div>
</div>
{/* Payment Table */}
<div className="bg-white rounded-lg shadow overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead>
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Date</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Shipper</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Route</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Freight</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Advance</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Paid to Driver</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{payments.map(payment => (
<tr key={payment.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm">{payment.date || 'N/A'}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm">{payment.shipper || payment.id}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm">{payment.from} {payment.to}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm">{parseFloat(payment.freight_charged || 0).toLocaleString()}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm">{parseFloat(payment.advance_received || 0).toLocaleString()}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm">{parseFloat(payment.paid_to_driver || 0).toLocaleString()}</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex px-2 py-1 text-xs rounded-full ${
payment.status === 'settled' ? 'bg-green-100 text-green-800' :
payment.status === 'pending collection' ? 'bg-yellow-100 text-yellow-800' :
'bg-gray-100 text-gray-800'
}`}>
{payment.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
export default Payments

130
src/components/Shippers.jsx Normal file
View file

@ -0,0 +1,130 @@
import React, { useState, useEffect } from 'react'
import { supabase } from '../services/supabase.js'
function Shippers() {
const [shippers, setShippers] = useState([])
const [loading, setLoading] = useState(true)
const [newShipper, setNewShipper] = useState({
name: '',
contact: '',
email: ''
})
useEffect(() => {
const fetchShippers = async () => {
try {
const { data, error } = await supabase
.from('shippers')
.select('*')
.order('created_at', { ascending: false })
if (error) throw error
setShippers(data || [])
} catch (error) {
console.error('Error fetching shippers:', error)
} finally {
setLoading(false)
}
}
fetchShippers()
}, [])
const handleSubmit = async (e) => {
e.preventDefault()
try {
const { error } = await supabase
.from('shippers')
.insert([newShipper])
if (error) throw error
// Refresh list
const { data } = await supabase
.from('shippers')
.select('*')
.order('created_at', { ascending: false })
setShippers(data || [])
setNewShipper({ name: '', contact: '', email: '' })
} catch (error) {
console.error('Error adding shipper:', error)
}
}
if (loading) return <div className="text-center py-8">Loading shippers...</div>
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Shippers</h2>
{/* Add Shipper Form */}
<div className="bg-white rounded-lg shadow p-6">
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Name</label>
<input
type="text"
value={newShipper.name}
onChange={(e) => setNewShipper({ ...newShipper, name: e.target.value })}
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Contact</label>
<input
type="tel"
value={newShipper.contact}
onChange={(e) => setNewShipper({ ...newShipper, contact: e.target.value })}
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input
type="email"
value={newShipper.email}
onChange={(e) => setNewShipper({ ...newShipper, email: e.target.value })}
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<button
type="submit"
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-800"
>
Add Shipper
</button>
</form>
</div>
{/* Shippers List */}
<div>
{shippers.length > 0 ? (
<div className="space-y-2">
{shippers.map(shipper => (
<ShipperCard key={shipper.id} shipper={shipper} />
))}
</div>
) : (
<p className="text-gray-500">No shippers found. Add your first shipper above!</p>
)}
</div>
</div>
)
}
function ShipperCard({ shipper }) {
return (
<div className="bg-white rounded-lg shadow p-4 border-l-4 border-green-500">
<div className="font-semibold">{shipper.name}</div>
<div className="text-sm text-gray-600">Contact: {shipper.contact}</div>
<div className="text-sm text-gray-600">Email: {shipper.email || 'N/A'}</div>
</div>
)
}
export default Shippers

3
src/index.css Normal file
View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

6
src/lib/supabase.ts Normal file
View file

@ -0,0 +1,6 @@
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.VITE_SUPABASE_URL as string,
process.env.VITE_SUPABASE_ANON_KEY as string
);

9
src/main.jsx Normal file
View file

@ -0,0 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
)

10
src/main.tsx Normal file
View file

@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

6
src/services/supabase.js Normal file
View file

@ -0,0 +1,6 @@
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseKey)

48
src/types/index.ts Normal file
View file

@ -0,0 +1,48 @@
export interface Load {
id: string;
date: string | null;
status: string;
vehicle: string | null;
from: string | null;
via: string | null;
to: string | null;
shipper: string | null;
load_type: string | null;
item: string | null;
deliveries: string | null;
freight_charged: number | null;
advance_received: number | null;
paid_to_driver: number | null;
commission: number | null;
driver_freight: number | null;
pending_from_shipper: number | null;
pending_to_driver: number | null;
notes: string | null;
created_at?: string;
updated_at?: string;
}
export type LoadFormData = Omit<Load, 'id' | 'created_at' | 'updated_at'>;
export const STATUS_OPTIONS = [
'settled',
'commission received',
'commission adjusted',
'commission due',
'pending collection',
'partially pending',
'fully pending from shipper',
'assigned vehicle',
'loaded / in transit',
'partial',
'available vehicle',
'pending lead',
'handled directly by shipper',
'reconciled',
] as const;
export const LOAD_TYPE_OPTIONS = [
'Full load',
'Part load',
'Mixed / multi-drop',
] as const;

8
tailwind.config.js Normal file
View file

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
}

6
vite.config.ts Normal file
View file

@ -0,0 +1,6 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
});