Added vet info
This commit is contained in:
@@ -24,6 +24,10 @@ type Bird = {
|
||||
motivators: string | null;
|
||||
demotivators: string | null;
|
||||
favoriteSnack: string | null;
|
||||
vetClinicName: string | null;
|
||||
vetClinicAddress: string | null;
|
||||
vetAccountNumber: string | null;
|
||||
vetDoctorName: string | null;
|
||||
gender: BirdGender;
|
||||
dateOfBirth: string | null;
|
||||
gotchaDay: string | null;
|
||||
@@ -219,6 +223,10 @@ type BirdFormState = {
|
||||
motivators: string;
|
||||
demotivators: string;
|
||||
favoriteSnack: string;
|
||||
vetClinicName: string;
|
||||
vetClinicAddress: string;
|
||||
vetAccountNumber: string;
|
||||
vetDoctorName: string;
|
||||
gender: BirdGender;
|
||||
dateOfBirth: string;
|
||||
gotchaDay: string;
|
||||
@@ -229,6 +237,8 @@ type BirdFormState = {
|
||||
publicProfileEnabled: boolean;
|
||||
};
|
||||
|
||||
type VeterinaryInfoFormState = Pick<BirdFormState, 'vetClinicName' | 'vetClinicAddress' | 'vetAccountNumber' | 'vetDoctorName'>;
|
||||
|
||||
type BirdImportWeight = {
|
||||
weightGrams: number;
|
||||
recordedOn: string;
|
||||
@@ -627,6 +637,10 @@ const emptyBirdForm: BirdFormState = {
|
||||
motivators: '',
|
||||
demotivators: '',
|
||||
favoriteSnack: '',
|
||||
vetClinicName: '',
|
||||
vetClinicAddress: '',
|
||||
vetAccountNumber: '',
|
||||
vetDoctorName: '',
|
||||
gender: 'unknown',
|
||||
dateOfBirth: '',
|
||||
gotchaDay: '',
|
||||
@@ -637,6 +651,13 @@ const emptyBirdForm: BirdFormState = {
|
||||
publicProfileEnabled: false,
|
||||
};
|
||||
|
||||
const emptyVeterinaryInfoForm: VeterinaryInfoFormState = {
|
||||
vetClinicName: '',
|
||||
vetClinicAddress: '',
|
||||
vetAccountNumber: '',
|
||||
vetDoctorName: '',
|
||||
};
|
||||
|
||||
const emptyMemorializeBirdForm = (): MemorializeBirdFormState => ({
|
||||
memorializedOn: new Date().toISOString().slice(0, 10),
|
||||
memorialNote: '',
|
||||
@@ -758,6 +779,10 @@ const toBirdForm = (bird: Bird): BirdFormState => ({
|
||||
motivators: parseBirdProfileList(bird.motivators).join('\n'),
|
||||
demotivators: parseBirdProfileList(bird.demotivators).join('\n'),
|
||||
favoriteSnack: bird.favoriteSnack ?? '',
|
||||
vetClinicName: bird.vetClinicName ?? '',
|
||||
vetClinicAddress: bird.vetClinicAddress ?? '',
|
||||
vetAccountNumber: bird.vetAccountNumber ?? '',
|
||||
vetDoctorName: bird.vetDoctorName ?? '',
|
||||
gender: bird.gender,
|
||||
dateOfBirth: bird.dateOfBirth ?? '',
|
||||
gotchaDay: bird.gotchaDay ?? '',
|
||||
@@ -768,6 +793,13 @@ const toBirdForm = (bird: Bird): BirdFormState => ({
|
||||
publicProfileEnabled: bird.publicProfileEnabled,
|
||||
});
|
||||
|
||||
const toVeterinaryInfoForm = (bird: Bird): VeterinaryInfoFormState => ({
|
||||
vetClinicName: bird.vetClinicName ?? '',
|
||||
vetClinicAddress: bird.vetClinicAddress ?? '',
|
||||
vetAccountNumber: bird.vetAccountNumber ?? '',
|
||||
vetDoctorName: bird.vetDoctorName ?? '',
|
||||
});
|
||||
|
||||
const formatDate = (value: string | null) => {
|
||||
if (!value) {
|
||||
return 'Not set';
|
||||
@@ -1538,6 +1570,7 @@ function App() {
|
||||
reason: '',
|
||||
notes: '',
|
||||
});
|
||||
const [veterinaryInfoForm, setVeterinaryInfoForm] = useState<VeterinaryInfoFormState>(emptyVeterinaryInfoForm);
|
||||
const [medicationForm, setMedicationForm] = useState({
|
||||
name: '',
|
||||
dosage: '',
|
||||
@@ -1562,6 +1595,8 @@ function App() {
|
||||
const [memorializingBird, setMemorializingBird] = useState(false);
|
||||
const [savingMemorialReminderBirdId, setSavingMemorialReminderBirdId] = useState('');
|
||||
const [editingVetVisitId, setEditingVetVisitId] = useState('');
|
||||
const [editingVeterinaryInfo, setEditingVeterinaryInfo] = useState(false);
|
||||
const [savingVeterinaryInfo, setSavingVeterinaryInfo] = useState(false);
|
||||
const [deletingVetVisitId, setDeletingVetVisitId] = useState('');
|
||||
const [editingMedicationId, setEditingMedicationId] = useState('');
|
||||
const [deletingMedicationId, setDeletingMedicationId] = useState('');
|
||||
@@ -1602,6 +1637,15 @@ function App() {
|
||||
setSelectedBirdTab('info');
|
||||
}, [selectedBirdId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedBird) {
|
||||
setVeterinaryInfoForm(toVeterinaryInfoForm(selectedBird));
|
||||
} else {
|
||||
setVeterinaryInfoForm(emptyVeterinaryInfoForm);
|
||||
}
|
||||
setEditingVeterinaryInfo(false);
|
||||
}, [selectedBird]);
|
||||
|
||||
const overviewWindowStartDate = useMemo(() => {
|
||||
const startDate = new Date();
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
@@ -2345,6 +2389,7 @@ function App() {
|
||||
setVetVisits([]);
|
||||
setMedications([]);
|
||||
setMedicationAdministrations([]);
|
||||
setVeterinaryInfoForm(emptyVeterinaryInfoForm);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3190,6 +3235,49 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleVeterinaryInfoSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!selectedBird || savingVeterinaryInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
setError('');
|
||||
setSavingVeterinaryInfo(true);
|
||||
|
||||
try {
|
||||
const response = await apiFetch(`/birds/${selectedBird.id}`, authToken, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
...toBirdForm(selectedBird),
|
||||
...veterinaryInfoForm,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await readErrorMessage(response, 'Unable to save veterinary info.'));
|
||||
}
|
||||
|
||||
const data = await readJsonSafely<{ bird?: Bird }>(response);
|
||||
if (!data?.bird) {
|
||||
throw new Error('Unable to save veterinary info.');
|
||||
}
|
||||
|
||||
const savedBird = data.bird;
|
||||
setBirds((current) => sortBirdsByName(current.map((bird) => (bird.id === savedBird.id ? savedBird : bird))));
|
||||
setVeterinaryInfoForm(toVeterinaryInfoForm(savedBird));
|
||||
setEditingVeterinaryInfo(false);
|
||||
if (editingBirdId === savedBird.id) {
|
||||
setBirdForm(toBirdForm(savedBird));
|
||||
}
|
||||
} catch (submitError) {
|
||||
setError(submitError instanceof Error ? submitError.message : 'Unable to save veterinary info.');
|
||||
} finally {
|
||||
setSavingVeterinaryInfo(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleWeightSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -5129,6 +5217,43 @@ function App() {
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<div className="settings-inline-header wide-field">
|
||||
<p className="eyebrow">Veterinary</p>
|
||||
<h3>Clinic account</h3>
|
||||
</div>
|
||||
<label>
|
||||
Clinic name
|
||||
<input
|
||||
value={birdForm.vetClinicName}
|
||||
onChange={(event) => setBirdForm({ ...birdForm, vetClinicName: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Account #
|
||||
<input
|
||||
value={birdForm.vetAccountNumber}
|
||||
onChange={(event) => setBirdForm({ ...birdForm, vetAccountNumber: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Dr. name
|
||||
<input
|
||||
value={birdForm.vetDoctorName}
|
||||
onChange={(event) => setBirdForm({ ...birdForm, vetDoctorName: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label className="wide-field">
|
||||
Clinic address
|
||||
<textarea
|
||||
rows={2}
|
||||
value={birdForm.vetClinicAddress}
|
||||
onChange={(event) => setBirdForm({ ...birdForm, vetClinicAddress: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label className="wide-field">
|
||||
Motivators
|
||||
<div className="profile-list-fields">
|
||||
@@ -5845,6 +5970,105 @@ function App() {
|
||||
<section className="panel inset-panel">
|
||||
<div className="panel-header">
|
||||
<div>
|
||||
<p className="eyebrow">Veterinary</p>
|
||||
<h2>Clinic account</h2>
|
||||
</div>
|
||||
{!editingVeterinaryInfo ? (
|
||||
<button
|
||||
className="profile-icon-button"
|
||||
onClick={() => {
|
||||
setVeterinaryInfoForm(toVeterinaryInfoForm(selectedBird));
|
||||
setEditingVeterinaryInfo(true);
|
||||
}}
|
||||
type="button"
|
||||
aria-label={`Edit veterinary info for ${selectedBird.name}`}
|
||||
title="Edit veterinary info"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<path d="M4 20h4l10.5-10.5-4-4L4 16v4Z" />
|
||||
<path d="m13.5 6.5 4 4" />
|
||||
<path d="M15 5l1.5-1.5a2.1 2.1 0 0 1 3 3L18 8" />
|
||||
</svg>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{editingVeterinaryInfo ? (
|
||||
<form className="form-panel inline-form care-entry-form" onSubmit={handleVeterinaryInfoSubmit}>
|
||||
<label>
|
||||
Clinic name
|
||||
<input
|
||||
value={veterinaryInfoForm.vetClinicName}
|
||||
onChange={(event) => setVeterinaryInfoForm({ ...veterinaryInfoForm, vetClinicName: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Account #
|
||||
<input
|
||||
value={veterinaryInfoForm.vetAccountNumber}
|
||||
onChange={(event) => setVeterinaryInfoForm({ ...veterinaryInfoForm, vetAccountNumber: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Dr. name
|
||||
<input
|
||||
value={veterinaryInfoForm.vetDoctorName}
|
||||
onChange={(event) => setVeterinaryInfoForm({ ...veterinaryInfoForm, vetDoctorName: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label className="wide-field">
|
||||
Clinic address
|
||||
<textarea
|
||||
rows={2}
|
||||
value={veterinaryInfoForm.vetClinicAddress}
|
||||
onChange={(event) => setVeterinaryInfoForm({ ...veterinaryInfoForm, vetClinicAddress: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<div className="button-row care-form-actions">
|
||||
<button className="primary-button" type="submit" disabled={savingVeterinaryInfo}>
|
||||
{savingVeterinaryInfo ? 'Saving veterinary info...' : 'Save veterinary info'}
|
||||
</button>
|
||||
<button
|
||||
className="secondary-button"
|
||||
onClick={() => {
|
||||
setVeterinaryInfoForm(toVeterinaryInfoForm(selectedBird));
|
||||
setEditingVeterinaryInfo(false);
|
||||
}}
|
||||
type="button"
|
||||
disabled={savingVeterinaryInfo}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="detail-grid">
|
||||
<article className="detail-card">
|
||||
<span>Clinic name</span>
|
||||
<strong>{selectedBird.vetClinicName || 'Not recorded'}</strong>
|
||||
</article>
|
||||
<article className="detail-card">
|
||||
<span>Account #</span>
|
||||
<strong>{selectedBird.vetAccountNumber || 'Not recorded'}</strong>
|
||||
</article>
|
||||
<article className="detail-card">
|
||||
<span>Dr. name</span>
|
||||
<strong>{selectedBird.vetDoctorName || 'Not recorded'}</strong>
|
||||
</article>
|
||||
<article className="detail-card wide-field">
|
||||
<span>Clinic address</span>
|
||||
<strong>{selectedBird.vetClinicAddress || 'Not recorded'}</strong>
|
||||
</article>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="panel inset-panel">
|
||||
<div className="panel-header">
|
||||
<div>
|
||||
<p className="eyebrow">Vet visits</p>
|
||||
<h2>Care history and notes</h2>
|
||||
</div>
|
||||
@@ -6894,6 +7118,43 @@ function App() {
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<div className="settings-inline-header wide-field">
|
||||
<p className="eyebrow">Veterinary</p>
|
||||
<h3>Clinic account</h3>
|
||||
</div>
|
||||
<label>
|
||||
Clinic name
|
||||
<input
|
||||
value={birdForm.vetClinicName}
|
||||
onChange={(event) => setBirdForm({ ...birdForm, vetClinicName: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Account #
|
||||
<input
|
||||
value={birdForm.vetAccountNumber}
|
||||
onChange={(event) => setBirdForm({ ...birdForm, vetAccountNumber: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Dr. name
|
||||
<input
|
||||
value={birdForm.vetDoctorName}
|
||||
onChange={(event) => setBirdForm({ ...birdForm, vetDoctorName: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label className="wide-field">
|
||||
Clinic address
|
||||
<textarea
|
||||
rows={2}
|
||||
value={birdForm.vetClinicAddress}
|
||||
onChange={(event) => setBirdForm({ ...birdForm, vetClinicAddress: event.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</label>
|
||||
<label className="wide-field">
|
||||
Motivators
|
||||
<div className="profile-list-fields">
|
||||
|
||||
Reference in New Issue
Block a user