SGA CARS PRODUCTIVITY
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 10/100
Línea de trabajo
El issue contiene un fragmento de despliegue de vercel.json y un panel de administración de React en un solo archivo, pero no indica qué debería cambiar. Primero hay que aclarar la funcionalidad prevista, el archivo de destino o la ubicación del proyecto y los criterios de aceptación; solo entonces se podrán identificar el punto de entrada relevante, las pruebas y la definición de terminado.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
// 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/[email protected]/dist/images/marker-icon-2x.png',
iconUrl: 'https://unpkg.com/[email protected]/dist/images/marker-icon.png',
shadowUrl: 'https://unpkg.com/[email protected]/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>
);
}
- Lenguaje dominante
- TypeScript
- Estrellas
- 5.2k
- Forks
- 1.9k
- Merge medio
- 1 h 4 min
- PR fusionados (30 d)
- 2
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Sin guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de vercel/examples
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
Los mantenedores suelen responder en 1 día
-
[next-chat] Typing indicator gets stuck when a socket disconnects mid-typingPosiblemente ocupada @officialasishkumar la tomó hace 12 días. Abierto
Dificultad 3/5 1-2 días Aptitud para principiantes 55/100
Los mantenedores suelen responder en 1 día
-
CARGO-TAXIAbierto
Dificultad 5/5 Más de una semana Aptitud para principiantes 10/100
Los mantenedores suelen responder en 1 día
-
Better ChoicezAbierto
Dificultad 5/5 Más de una semana Aptitud para principiantes 15/100
vercel/examples#1584 · 2 reacciones ·
Los mantenedores suelen responder en 1 día
-
onbeat landing pageAbierto
Dificultad 4/5 3-5 días Aptitud para principiantes 32/100
Los mantenedores suelen responder en 1 día
Todos los issues de vercel/examples
Issues similares
-
[BUG]: Duplicate preposition in heading and unformatted feedback text on Strengthen Argument pageAbiertobug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
AOSSIE-Org/DebateAI#801 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Los mantenedores suelen responder en 1 día
-
ci: repo has no GitHub Actions workflow, so pushes and pull requests are never built or testedAbiertoagents area: runtime automation chore cli consistency dependencies developer-experience difficulty: easy documentation enhancement good first issue help wanted hygiene json-output priority: P3 starters tests
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
-
Add: BOM CineAbiertoapproved check:passed streams:add
Dificultad 1/5 Menos de una hora Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día