summaryrefslogtreecommitdiff
path: root/frontend/src/components/ContractDetail.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/ContractDetail.tsx')
-rw-r--r--frontend/src/components/ContractDetail.tsx232
1 files changed, 232 insertions, 0 deletions
diff --git a/frontend/src/components/ContractDetail.tsx b/frontend/src/components/ContractDetail.tsx
new file mode 100644
index 0000000..72527ce
--- /dev/null
+++ b/frontend/src/components/ContractDetail.tsx
@@ -0,0 +1,232 @@
+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
+ 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>
+ )
+}