vercel / vercel/examples

SGA CARS PRODUCTIVITY

Open
#1,263 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
5.2k
Forks
1.8k
Avg merge
3d 12h
Merged PRs (30d)
6

Description

// vercel.json configuration for deployment
// Place this file in the project root when deploying to Vercel
// Build command: npm run build
// Output directory: dist

{
"version": 2,
"builds": [
{ "src": "package.json", "use": "@vercel/static-build" }
],
"routes": [
{ "src": "/(.*)", "dest": "/index.html" }
]
}

/*
SGA Productivity - Web Admin Dashboard
Single-file React component (default export) ready for a Vite / Create React App project.

How to deploy directly online (Vercel):
1️⃣ Create a GitHub repository and push your project.
2️⃣ Go to https://vercel.com → Import Project → Select your repo.
3️⃣ Use these settings:
• Framework: React
• Build command: npm run build
• Output directory: dist
4️⃣ Once deployed, Vercel will generate a public URL like https://sga-productivity.vercel.app.
*/

import React, { useEffect, useState, useRef } from 'react';
import axios from 'axios';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';

delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
});

const API_BASE = process.env.REACT_APP_API_BASE || 'https://your-backend-url/api';
const STATUS_OPTIONS = ['assigned','in_progress','road_test','final_inspection','qc_pending','completed'];

export default function App() {
const [token, setToken] = useState(() => localStorage.getItem('sga_token') || '');
const [user, setUser] = useState(() => {
try { return JSON.parse(localStorage.getItem('sga_user') || 'null'); } catch { return null; }
});
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [jobs, setJobs] = useState([]);
const [locations, setLocations] = useState([]);
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [showCreate, setShowCreate] = useState(false);
const [newJob, setNewJob] = useState({ title:'', vehicle_reg:'', description:'', assigned_to:'' });
const refreshIntervalRef = useRef(null);

useEffect(() => {
if (token) {
axios.defaults.headers.common['Authorization'] = Bearer ${token};
fetchAll();
refreshIntervalRef.current = setInterval(() => { fetchLocations(); fetchJobs(); }, 15000);
} else {
delete axios.defaults.headers.common['Authorization'];
}
return () => {
if (refreshIntervalRef.current) {
clearInterval(refreshIntervalRef.current);
refreshIntervalRef.current = null;
}
};
}, [token]);

async function fetchJobs(){
try{ const r = await axios.get(${API_BASE}/jobs); setJobs(Array.isArray(r.data)?r.data:[]); }
catch(err){ setError('Failed to load jobs'); }
}
async function fetchLocations(){
try{ const r = await axios.get(${API_BASE}/locations/latest); setLocations(Array.isArray(r.data)?r.data:[]); }
catch(err){ setError('Failed to load locations'); }
}
async function fetchUsers(){
try{ const r = await axios.get(${API_BASE}/users); setUsers(Array.isArray(r.data)?r.data:[]); }
catch(err){ console.warn('fetchUsers warning', err); }
}
async function fetchAll(){ setLoading(true); await Promise.all([fetchJobs(),fetchLocations(),fetchUsers()]); setLoading(false); }

async function handleLogin(e){
e.preventDefault(); setError('');
try{
const r = await axios.post(${API_BASE}/login,{email,password});
if(r.data.token){ setToken(r.data.token); setUser(r.data.user||null); localStorage.setItem('sga_token',r.data.token); localStorage.setItem('sga_user',JSON.stringify(r.data.user||null)); }
else setError('Invalid login');
}catch{ setError('Login failed'); }
}

function handleLogout(){ setToken(''); setUser(null); localStorage.clear(); }
async function handleCreateJob(e){ e.preventDefault(); try{ await axios.post(${API_BASE}/jobs,{...newJob,priority:3}); setShowCreate(false); fetchJobs(); }catch{ setError('Failed to create job'); } }
async function updateJobStatus(id,s){ try{ await axios.patch(${API_BASE}/jobs/${id}/status,{status:s}); fetchJobs(); }catch{ setError('Failed to update job'); } }

const mapCenter=[12.9716,77.5946];

return (




SGA Productivity — Admin Dashboard


{user?Logout:null}

  <main className="max-w-7xl mx-auto p-6 grid grid-cols-12 gap-6">
    {!token?(
      <section className="col-span-12 bg-white p-6 rounded shadow">
        <h2 className="text-lg font-semibold mb-3">Login</h2>
        <form onSubmit={handleLogin} className="grid grid-cols-2 gap-4">
          <input className="col-span-2 p-3 border rounded" placeholder="Email" value={email} onChange={e=>setEmail(e.target.value)} />
          <input className="col-span-2 p-3 border rounded" placeholder="Password" type="password" value={password} onChange={e=>setPassword(e.target.value)} />
          <button className="col-span-2 p-3 bg-blue-600 text-white rounded" type="submit">Login</button>
        </form>
        {error&&<div className="mt-3 text-red-600">{error}</div>}
      </section>
    ):(
      <React.Fragment>
        <section className="col-span-8 bg-white p-4 rounded shadow">
          <h2 className="text-lg font-semibold mb-3">Live Technician Map</h2>
          <div style={{height:'60vh'}} className="rounded overflow-hidden border">
            <MapContainer center={mapCenter} zoom={12} style={{height:'100%',width:'100%'}}>
              <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
              {locations.map(loc=>(
                <Marker key={String(loc.user_id)} position={[parseFloat(loc.latitude),parseFloat(loc.longitude)]}>
                  <Popup>
                    <div className="text-sm"><div className="font-semibold">{loc.name}</div><div className="text-xs">{loc.recorded_at?new Date(loc.recorded_at).toLocaleString():''}</div></div>
                  </Popup>
                </Marker>
              ))}
            </MapContainer>
          </div>
        </section>
        <section className="col-span-4 space-y-4">
          <div className="bg-white p-4 rounded shadow">
            <h3 className="font-semibold text-md mb-2">Jobs</h3>
            <div className="space-y-2 max-h-64 overflow-auto">
              {jobs.map(j=>(
                <div key={String(j.id)} className="p-2 border rounded flex justify-between">
                  <div><div className="font-medium">{j.title}</div><div className="text-xs">{j.vehicle_reg}</div></div>
                  <div><div className="text-xs bg-gray-100 px-2 py-1 rounded">{j.status}</div>{STATUS_OPTIONS.map(s=>(<button key={s} onClick={()=>updateJobStatus(j.id,s)} className="text-xs px-2 py-1 bg-gray-100 rounded">{s}</button>))}</div>
                </div>
              ))}
            </div>
          </div>
        </section>
      </React.Fragment>
    )}
  </main>

  <footer className="text-center p-4 text-sm text-gray-500">SGA Productivity • Admin Dashboard</footer>
</div>

);
}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue contains a vercel.json deployment snippet and a single-file React admin dashboard, but it does not state what should change. First clarify the intended feature, target file or project location, and acceptance criteria; only then can the relevant entry point, tests, and definition of done be identified.

Written by the indexing model from the issue text.

Assessment

Tech stack
react
Domain
cloud, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
10/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.