SGA CARS PRODUCTIVITY

Abierto
#1,263 0 comentarios 0 reacciones 0 asignados Ver en GitHub

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
Tipo de issue
Nueva funcionalidad
Claridad
Necesita aclaración
Estado de actividad
Estancado
Stack tecnológico
react
Área
cloud, frontend

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

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de vercel/examples

Todos los issues de vercel/examples

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.