Phase 2 Complete by Claude Code

This commit is contained in:
2026-02-17 00:10:37 +02:00
parent 5e2d4b6b1b
commit 2b48426fe5
17 changed files with 1671 additions and 14 deletions

View File

@@ -1 +1,31 @@
// TODO: Confirmation dialog component
export default function ConfirmDialog({ open, title, message, onConfirm, onCancel }) {
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="fixed inset-0 bg-black/50" onClick={onCancel} />
<div className="relative bg-white rounded-lg shadow-xl p-6 w-full max-w-sm mx-4">
<h3 className="text-lg font-semibold text-gray-900 mb-2">
{title || "Confirm"}
</h3>
<p className="text-sm text-gray-600 mb-6">
{message || "Are you sure?"}
</p>
<div className="flex justify-end gap-3">
<button
onClick={onCancel}
className="px-4 py-2 text-sm text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors"
>
Cancel
</button>
<button
onClick={onConfirm}
className="px-4 py-2 text-sm text-white bg-red-600 rounded-md hover:bg-red-700 transition-colors"
>
Delete
</button>
</div>
</div>
</div>
);
}

View File

@@ -1 +1,48 @@
// TODO: Reusable table component
export default function DataTable({ columns, data, onRowClick, emptyMessage = "No data found." }) {
if (!data || data.length === 0) {
return (
<div className="bg-white rounded-lg border border-gray-200 p-8 text-center text-gray-500 text-sm">
{emptyMessage}
</div>
);
}
return (
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-gray-50 border-b border-gray-200">
{columns.map((col) => (
<th
key={col.key}
className="px-4 py-3 text-left font-medium text-gray-600"
style={col.width ? { width: col.width } : {}}
>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, idx) => (
<tr
key={row.id || idx}
onClick={() => onRowClick?.(row)}
className={`border-b border-gray-100 last:border-0 ${
onRowClick ? "cursor-pointer hover:bg-gray-50" : ""
}`}
>
{columns.map((col) => (
<td key={col.key} className="px-4 py-3 text-gray-700">
{col.render ? col.render(row) : row[col.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}

View File

@@ -1 +1,44 @@
// TODO: Search bar component
import { useState } from "react";
export default function SearchBar({ onSearch, placeholder = "Search..." }) {
const [value, setValue] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
onSearch(value);
};
const handleClear = () => {
setValue("");
onSearch("");
};
return (
<form onSubmit={handleSubmit} className="flex gap-2">
<div className="relative flex-1">
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder={placeholder}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
/>
{value && (
<button
type="button"
onClick={handleClear}
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
&times;
</button>
)}
</div>
<button
type="submit"
className="px-4 py-2 bg-blue-600 text-white text-sm rounded-md hover:bg-blue-700 transition-colors"
>
Search
</button>
</form>
);
}