SGA CARS PRODUCTIVITY
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 10/100
調査の方向性
Issue には vercel.json のデプロイメントスニペットと単一ファイルの React 管理ダッシュボードが含まれていますが、何を変更すべきかは記載されていません。まず、意図された機能、対象ファイルまたはプロジェクトの場所、受け入れ基準を明確にしてください。その後で初めて、関連するエントリーポイント、テスト、完了の定義を特定できます。
索引モデルが issue の本文から書いたものです。
説明
// 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>
);
}
- 主要言語
- TypeScript
- スター
- 5.2k
- フォーク
- 1.9k
- 平均マージ
- 1時間 4分
- マージ済み PR(30日)
- 2
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
vercel/examples のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
[next-chat] Typing indicator gets stuck when a socket disconnects mid-typing対応中かも @officialasishkumar が 12 日前に担当しました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 55/100
メンテナーはふだん 1 日以内に返信
-
CARGO-TAXIオープン
難易度 5/5 1週間以上 初心者へのやさしさ 10/100
メンテナーはふだん 1 日以内に返信
-
Better Choicezオープン
難易度 5/5 1週間以上 初心者へのやさしさ 15/100
vercel/examples#1584 · リアクション 2 件 ·
メンテナーはふだん 1 日以内に返信
-
onbeat landing pageオープン
難易度 4/5 3〜5日 初心者へのやさしさ 32/100
メンテナーはふだん 1 日以内に返信
vercel/examples の issue をすべて見る
似ている issue
-
[BUG]: Duplicate preposition in heading and unformatted feedback text on Strengthen Argument pageオープンbug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
AOSSIE-Org/DebateAI#801 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信
-
agents 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
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
-
Add: BOM Cineオープンapproved check:passed streams:add
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信