Add Prettier for TypeScript formatting

- Install prettier
- Configure .prettierrc.json and .prettierignore
- Add npm scripts: format, format:check
- Add Makefile target: format-frontend
- Format all frontend files
This commit is contained in:
counterweight 2025-12-21 21:59:26 +01:00
parent 4b394b0698
commit 37de6f70e0
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
44 changed files with 906 additions and 856 deletions

View file

@ -234,14 +234,17 @@ export default function BookingPage() {
const [isLoadingAvailability, setIsLoadingAvailability] = useState(true);
// Memoize dates to prevent infinite re-renders
const dates = useMemo(() => getDateRange(minAdvanceDays, maxAdvanceDays), [minAdvanceDays, maxAdvanceDays]);
const dates = useMemo(
() => getDateRange(minAdvanceDays, maxAdvanceDays),
[minAdvanceDays, maxAdvanceDays]
);
const fetchSlots = useCallback(async (date: Date) => {
setIsLoadingSlots(true);
setError(null);
setAvailableSlots([]);
setSelectedSlot(null);
try {
const dateStr = formatDate(date);
const data = await api.get<AvailableSlotsResponse>(`/api/booking/slots?date=${dateStr}`);
@ -261,7 +264,7 @@ export default function BookingPage() {
const fetchAllAvailability = async () => {
setIsLoadingAvailability(true);
const availabilitySet = new Set<string>();
// Fetch availability for all dates in parallel
const promises = dates.map(async (date) => {
try {
@ -275,7 +278,7 @@ export default function BookingPage() {
console.error(`Failed to fetch availability for ${formatDate(date)}:`, err);
}
});
await Promise.all(promises);
setDatesWithAvailability(availabilitySet);
setIsLoadingAvailability(false);
@ -307,20 +310,22 @@ export default function BookingPage() {
const handleBook = async () => {
if (!selectedSlot) return;
setIsBooking(true);
setError(null);
try {
const appointment = await api.post<AppointmentResponse>("/api/booking", {
slot_start: selectedSlot.start_time,
note: note || null,
});
setSuccessMessage(`Appointment booked for ${formatTime(appointment.slot_start)} - ${formatTime(appointment.slot_end)}`);
setSuccessMessage(
`Appointment booked for ${formatTime(appointment.slot_start)} - ${formatTime(appointment.slot_end)}`
);
setSelectedSlot(null);
setNote("");
// Refresh slots to show the booked one is gone
if (selectedDate) {
await fetchSlots(selectedDate);
@ -359,13 +364,9 @@ export default function BookingPage() {
Select a date to see available {slotDurationMinutes}-minute slots
</p>
{successMessage && (
<div style={styles.successBanner}>{successMessage}</div>
)}
{successMessage && <div style={styles.successBanner}>{successMessage}</div>}
{error && (
<div style={styles.errorBanner}>{error}</div>
)}
{error && <div style={styles.errorBanner}>{error}</div>}
{/* Date Selection */}
<div style={styles.section}>
@ -376,7 +377,7 @@ export default function BookingPage() {
const isSelected = selectedDate && formatDate(selectedDate) === dateStr;
const hasAvailability = datesWithAvailability.has(dateStr);
const isDisabled = !hasAvailability || isLoadingAvailability;
return (
<button
key={dateStr}
@ -404,13 +405,14 @@ export default function BookingPage() {
{selectedDate && (
<div style={styles.section}>
<h2 style={styles.sectionTitle}>
Available Slots for {selectedDate.toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric"
Available Slots for{" "}
{selectedDate.toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
})}
</h2>
{isLoadingSlots ? (
<div style={styles.emptyState}>Loading slots...</div>
) : availableSlots.length === 0 ? (
@ -442,27 +444,28 @@ export default function BookingPage() {
<div style={styles.confirmCard}>
<h3 style={styles.confirmTitle}>Confirm Booking</h3>
<p style={styles.confirmTime}>
<strong>Time:</strong> {formatTime(selectedSlot.start_time)} - {formatTime(selectedSlot.end_time)}
<strong>Time:</strong> {formatTime(selectedSlot.start_time)} -{" "}
{formatTime(selectedSlot.end_time)}
</p>
<div>
<label style={styles.inputLabel}>
Note (optional, max {noteMaxLength} chars)
</label>
<label style={styles.inputLabel}>Note (optional, max {noteMaxLength} chars)</label>
<textarea
value={note}
onChange={(e) => setNote(e.target.value.slice(0, noteMaxLength))}
placeholder="Add a note about your appointment..."
style={styles.textarea}
/>
<div style={{
...styles.charCount,
...(note.length >= noteMaxLength ? styles.charCountWarning : {}),
}}>
<div
style={{
...styles.charCount,
...(note.length >= noteMaxLength ? styles.charCountWarning : {}),
}}
>
{note.length}/{noteMaxLength}
</div>
</div>
<div style={styles.buttonRow}>
<button
onClick={handleBook}
@ -488,4 +491,3 @@ export default function BookingPage() {
</main>
);
}