gmal-scope-builder/frontend/src/pages/NewProject.tsx
DJP e18976fdb2 Initial commit - GMAL Scope Builder
Dockerized web app (FastAPI + React + PostgreSQL) for scoping client ratecards
against the GMAL master asset database. Features:
- GMAL data ingestion from Excel (390 assets, 120 roles, 5 model types)
- AI-powered document parsing and asset extraction (Claude Opus 4.6)
- AI matching engine with parallel batching, confidence scoring, caveats
- Ratecard builder with hours x volume calculation
- Excel and PDF export
- GMAL browser and inline editor
- AI cost tracking per project (persisted to DB)
- Debug panel for AI call inspection
- Dark theme UI with gold (#FFC407) accent

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:35:14 -04:00

87 lines
2.9 KiB
TypeScript

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import api from '../api/client';
import { MODEL_TYPE_LABELS } from '../types';
import './NewProject.css';
export default function NewProject() {
const navigate = useNavigate();
const [name, setName] = useState('');
const [clientName, setClientName] = useState('');
const [description, setDescription] = useState('');
const [modelType, setModelType] = useState('current_oplus');
const [creating, setCreating] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!name.trim()) return;
setCreating(true);
try {
const res = await api.post('/projects', {
name: name.trim(),
client_name: clientName.trim() || null,
description: description.trim() || null,
model_type: modelType,
});
navigate(`/projects/${res.data.id}`);
} catch (err: any) {
alert(`Failed to create project: ${err.response?.data?.detail || err.message}`);
setCreating(false);
}
}
return (
<div className="new-project">
<h1 className="page-title">New Project</h1>
<form onSubmit={handleSubmit} className="form-card">
<div className="field">
<label className="label">Project Name <span className="required">*</span></label>
<input
className="input"
value={name}
onChange={e => setName(e.target.value)}
placeholder="e.g. Acme Corp Q2 2025 Scope"
required
/>
</div>
<div className="field">
<label className="label">Client Name</label>
<input
className="input"
value={clientName}
onChange={e => setClientName(e.target.value)}
placeholder="e.g. Acme Corp"
/>
</div>
<div className="field">
<label className="label">Description</label>
<textarea
className="input textarea"
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="Brief description of this scope..."
/>
</div>
<div className="field">
<label className="label">Model Type</label>
<select className="input" value={modelType} onChange={e => setModelType(e.target.value)}>
{Object.entries(MODEL_TYPE_LABELS).map(([value, label]) => (
<option key={value} value={value}>{label}</option>
))}
</select>
</div>
<div className="form-actions">
<button type="button" onClick={() => navigate('/')} className="btn btn-secondary">Cancel</button>
<button type="submit" disabled={creating || !name.trim()} className="btn btn-primary">
{creating ? 'Creating...' : 'Create Project'}
</button>
</div>
</form>
</div>
);
}