blob: 135c3137dc465dc7ee002d13b128abb8323f4b9e (
plain) (
tree)
|
|
import React, { useEffect, useState } from 'react'
import { useParams, Link } from 'react-router-dom'
interface FileSummary {
id: string
name: string
description?: string
contract_phase?: string
}
interface TaskSummary {
id: string
name: string
status: string
}
interface ContractRepository {
id: string
name: string
source_type: string
is_primary: boolean
}
interface Contract {
id: string
name: string
description?: string
contract_type: string
phase: string
status: string
localOnly?: boolean
autoMergeLocal?: boolean
version: number
created_at: string
}
interface ContractWithRelations {
contract: Contract
repositories: ContractRepository[]
files: FileSummary[]
tasks: TaskSummary[]
}
type Tab = 'overview' | 'files' | 'tasks' | 'repositories'
export function ContractDetail() {
const { id } = useParams<{ id: string }>()
const [data, setData] = useState<ContractWithRelations | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [activeTab, setActiveTab] = useState<Tab>('overview')
useEffect(() => {
async function fetchContract() {
if (!id) return
try {
setLoading(true)
const response = await fetch(`/api/v1/contracts/${id}`)
if (!response.ok) {
throw new Error(`Failed to fetch contract: ${response.statusText}`)
}
const contractData = await response.json()
setData(contractData)
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error')
} finally {
setLoading(false)
}
}
fetchContract()
}, [id])
if (loading) {
return (
<div className="contract-detail-container">
<div className="loading">Loading contract...</div>
</div>
)
}
if (error) {
return (
<div className="contract-detail-container">
<div className="error">Error: {error}</div>
<Link to="/contracts" className="back-link">
Back to Contracts
</Link>
</div>
)
}
if (!data) {
return (
<div className="contract-detail-container">
<div className="not-found">Contract not found</div>
<Link to="/contracts" className="back-link">
Back to Contracts
</Link>
</div>
)
}
const { contract, repositories, files, tasks } = data
return (
<div className="contract-detail-container">
<div className="contract-detail-header">
<Link to="/contracts" className="back-link">
Back to Contracts
</Link>
<h1 className="contract-title">{contract.name}</h1>
{contract.description && (
<p className="contract-description">{contract.description}</p>
)}
<div className="contract-meta">
<span>Phase: {contract.phase}</span>
<span>Status: {contract.status}</span>
<span>Version: {contract.version}</span>
</div>
</div>
<div className="contract-tabs">
<button
className={`tab-button ${activeTab === 'overview' ? 'active' : ''}`}
onClick={() => setActiveTab('overview')}
>
Overview
</button>
<button
className={`tab-button ${activeTab === 'files' ? 'active' : ''}`}
onClick={() => setActiveTab('files')}
>
Files ({files.length})
</button>
<button
className={`tab-button ${activeTab === 'tasks' ? 'active' : ''}`}
onClick={() => setActiveTab('tasks')}
>
Tasks ({tasks.length})
</button>
<button
className={`tab-button ${activeTab === 'repositories' ? 'active' : ''}`}
onClick={() => setActiveTab('repositories')}
>
Repositories ({repositories.length})
</button>
</div>
<div className="contract-tab-content">
{activeTab === 'overview' && (
<div className="tab-panel">
<h2>Contract Overview</h2>
<dl className="overview-list">
<dt>Type</dt>
<dd>{contract.contract_type}</dd>
<dt>Phase</dt>
<dd>{contract.phase}</dd>
<dt>Status</dt>
<dd>{contract.status}</dd>
<dt>Created</dt>
<dd>{new Date(contract.created_at).toLocaleString()}</dd>
</dl>
</div>
)}
{activeTab === 'files' && (
<div className="tab-panel">
<h2>Files</h2>
{files.length === 0 ? (
<p>No files in this contract</p>
) : (
<ul className="file-list">
{files.map((file) => (
<li key={file.id} className="file-item">
<Link to={`/contracts/${contract.id}/files/${file.id}`}>
<h3>{file.name}</h3>
{file.description && <p>{file.description}</p>}
{file.contract_phase && (
<span className="file-phase">Phase: {file.contract_phase}</span>
)}
</Link>
</li>
))}
</ul>
)}
</div>
)}
{activeTab === 'tasks' && (
<div className="tab-panel">
<h2>Tasks</h2>
{tasks.length === 0 ? (
<p>No tasks in this contract</p>
) : (
<ul className="task-list">
{tasks.map((task) => (
<li key={task.id} className="task-item">
<h3>{task.name}</h3>
<span className={`task-status status-${task.status}`}>
{task.status}
</span>
</li>
))}
</ul>
)}
</div>
)}
{activeTab === 'repositories' && (
<div className="tab-panel">
<h2>Repositories</h2>
{repositories.length === 0 ? (
<p>No repositories linked to this contract</p>
) : (
<ul className="repository-list">
{repositories.map((repo) => (
<li key={repo.id} className="repository-item">
<h3>
{repo.name}
{repo.is_primary && <span className="primary-badge">Primary</span>}
</h3>
<span className="repo-type">{repo.source_type}</span>
</li>
))}
</ul>
)}
</div>
)}
</div>
</div>
)
}
|