134 lines
4.6 KiB
TypeScript
134 lines
4.6 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import type { castrations } from "~/database/schema";
|
|
|
|
interface CastrationTrackerProps {
|
|
castrations: (typeof castrations.$inferSelect)[];
|
|
totalCount: number;
|
|
}
|
|
|
|
export function CastrationTracker({
|
|
castrations: initialCastrations,
|
|
totalCount: initialCount,
|
|
}: CastrationTrackerProps) {
|
|
const [castrations, setCastrations] = useState(initialCastrations);
|
|
const [totalCount, setTotalCount] = useState(initialCount);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const handleCastration = async (gender: "male" | "female") => {
|
|
setLoading(true);
|
|
try {
|
|
const response = await fetch("/api/castration", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ gender }),
|
|
});
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
|
|
// Add new castration to the list
|
|
const newCastration = {
|
|
id: castrations.length + 1,
|
|
gender,
|
|
timestamp: new Date(),
|
|
};
|
|
|
|
setCastrations([newCastration, ...castrations]);
|
|
setTotalCount(totalCount + 1);
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to record castration:", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const formatTime = (date: Date | string) => {
|
|
const d = typeof date === "string" ? new Date(date) : date;
|
|
return d.toLocaleString();
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 p-8">
|
|
<div className="max-w-2xl mx-auto">
|
|
{/* Header */}
|
|
<div className="text-center mb-12">
|
|
<h1 className="text-4xl font-bold text-gray-800 mb-2">
|
|
Castration Tracker
|
|
</h1>
|
|
<div className="bg-white rounded-lg shadow-md p-6">
|
|
<p className="text-3xl font-bold text-indigo-600">{totalCount}</p>
|
|
<p className="text-gray-600 text-lg">Total Castrations</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Buttons */}
|
|
<div className="grid grid-cols-2 gap-6 mb-12">
|
|
<button
|
|
onClick={() => handleCastration("male")}
|
|
disabled={loading}
|
|
className="bg-blue-500 hover:bg-blue-600 disabled:bg-blue-300 text-white font-bold py-12 px-6 rounded-lg shadow-lg transition duration-200 transform hover:scale-105 active:scale-95"
|
|
>
|
|
<div className="text-6xl mb-4">♂</div>
|
|
<div className="text-xl">Male</div>
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => handleCastration("female")}
|
|
disabled={loading}
|
|
className="bg-pink-500 hover:bg-pink-600 disabled:bg-pink-300 text-white font-bold py-12 px-6 rounded-lg shadow-lg transition duration-200 transform hover:scale-105 active:scale-95"
|
|
>
|
|
<div className="text-6xl mb-4">♀</div>
|
|
<div className="text-xl">Female</div>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Castration List */}
|
|
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
|
|
<div className="bg-indigo-600 text-white p-4">
|
|
<h2 className="text-2xl font-bold">Recent Castrations</h2>
|
|
</div>
|
|
|
|
{castrations.length > 0 ? (
|
|
<div className="divide-y">
|
|
{castrations.map((castration) => (
|
|
<div
|
|
key={castration.id}
|
|
className="p-4 hover:bg-gray-50 transition flex items-center justify-between"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div
|
|
className={`text-4xl ${
|
|
castration.gender === "male"
|
|
? "text-blue-500"
|
|
: "text-pink-500"
|
|
}`}
|
|
>
|
|
{castration.gender === "male" ? "♂" : "♀"}
|
|
</div>
|
|
<div>
|
|
<p className="font-semibold text-gray-800 capitalize">
|
|
{castration.gender}
|
|
</p>
|
|
<p className="text-gray-600 text-sm">
|
|
{formatTime(castration.timestamp)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="text-gray-400 text-sm">#{castration.id}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="p-8 text-center text-gray-500">
|
|
<p>No castrations recorded yet. Start by clicking a button above!</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|