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:
parent
4b394b0698
commit
37de6f70e0
44 changed files with 906 additions and 856 deletions
|
|
@ -189,7 +189,7 @@ export default function AdminAppointmentsPage() {
|
|||
const handleCancel = async (appointmentId: number) => {
|
||||
setCancellingId(appointmentId);
|
||||
setError(null);
|
||||
|
||||
|
||||
try {
|
||||
await api.post<AppointmentResponse>(`/api/admin/appointments/${appointmentId}/cancel`, {});
|
||||
await fetchAppointments();
|
||||
|
|
@ -225,13 +225,9 @@ export default function AdminAppointmentsPage() {
|
|||
<Header currentPage="admin-appointments" />
|
||||
<div style={styles.content}>
|
||||
<h1 style={styles.pageTitle}>All Appointments</h1>
|
||||
<p style={styles.pageSubtitle}>
|
||||
View and manage all user appointments
|
||||
</p>
|
||||
<p style={styles.pageSubtitle}>View and manage all user appointments</p>
|
||||
|
||||
{error && (
|
||||
<div style={styles.errorBanner}>{error}</div>
|
||||
)}
|
||||
{error && <div style={styles.errorBanner}>{error}</div>}
|
||||
|
||||
{/* Status Filter */}
|
||||
<div style={styles.filterRow}>
|
||||
|
|
@ -269,26 +265,20 @@ export default function AdminAppointmentsPage() {
|
|||
>
|
||||
<div style={styles.appointmentHeader}>
|
||||
<div>
|
||||
<div style={styles.appointmentTime}>
|
||||
{formatDateTime(apt.slot_start)}
|
||||
</div>
|
||||
<div style={styles.appointmentUser}>
|
||||
{apt.user_email}
|
||||
</div>
|
||||
{apt.note && (
|
||||
<div style={styles.appointmentNote}>
|
||||
"{apt.note}"
|
||||
</div>
|
||||
)}
|
||||
<span style={{
|
||||
...styles.statusBadge,
|
||||
background: status.bgColor,
|
||||
color: status.textColor,
|
||||
}}>
|
||||
<div style={styles.appointmentTime}>{formatDateTime(apt.slot_start)}</div>
|
||||
<div style={styles.appointmentUser}>{apt.user_email}</div>
|
||||
{apt.note && <div style={styles.appointmentNote}>"{apt.note}"</div>}
|
||||
<span
|
||||
style={{
|
||||
...styles.statusBadge,
|
||||
background: status.bgColor,
|
||||
color: status.textColor,
|
||||
}}
|
||||
>
|
||||
{status.text}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
{apt.status === "booked" && (
|
||||
<div style={styles.buttonGroup}>
|
||||
{confirmCancelId === apt.id ? (
|
||||
|
|
@ -327,4 +317,3 @@ export default function AdminAppointmentsPage() {
|
|||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,13 @@ import { Header } from "../../components/Header";
|
|||
import { useRequireAuth } from "../../hooks/useRequireAuth";
|
||||
import { components } from "../../generated/api";
|
||||
import constants from "../../../../shared/constants.json";
|
||||
import { formatDate, formatDisplayDate, getDateRange, formatTimeString, isWeekend } from "../../utils/date";
|
||||
import {
|
||||
formatDate,
|
||||
formatDisplayDate,
|
||||
getDateRange,
|
||||
formatTimeString,
|
||||
isWeekend,
|
||||
} from "../../utils/date";
|
||||
|
||||
const { slotDurationMinutes, maxAdvanceDays, minAdvanceDays } = constants.booking;
|
||||
|
||||
|
|
@ -57,14 +63,14 @@ export default function AdminAvailabilityPage() {
|
|||
const fetchAvailability = useCallback(async () => {
|
||||
const dateRange = getDateRange(minAdvanceDays, maxAdvanceDays);
|
||||
if (!dateRange.length) return;
|
||||
|
||||
|
||||
try {
|
||||
const fromDate = formatDate(dateRange[0]);
|
||||
const toDate = formatDate(dateRange[dateRange.length - 1]);
|
||||
const data = await api.get<AvailabilityResponse>(
|
||||
`/api/admin/availability?from=${fromDate}&to=${toDate}`
|
||||
);
|
||||
|
||||
|
||||
const map = new Map<string, TimeSlot[]>();
|
||||
for (const day of data.days) {
|
||||
map.set(day.date, day.slots);
|
||||
|
|
@ -118,21 +124,21 @@ export default function AdminAvailabilityPage() {
|
|||
|
||||
const saveAvailability = async () => {
|
||||
if (!selectedDate) return;
|
||||
|
||||
|
||||
setIsSaving(true);
|
||||
setError(null);
|
||||
|
||||
|
||||
try {
|
||||
const slots = editSlots.map((s) => ({
|
||||
start_time: s.start_time + ":00",
|
||||
end_time: s.end_time + ":00",
|
||||
}));
|
||||
|
||||
|
||||
await api.put("/api/admin/availability", {
|
||||
date: formatDate(selectedDate),
|
||||
slots,
|
||||
});
|
||||
|
||||
|
||||
await fetchAvailability();
|
||||
closeModal();
|
||||
} catch (err) {
|
||||
|
|
@ -144,16 +150,16 @@ export default function AdminAvailabilityPage() {
|
|||
|
||||
const clearAvailability = async () => {
|
||||
if (!selectedDate) return;
|
||||
|
||||
|
||||
setIsSaving(true);
|
||||
setError(null);
|
||||
|
||||
|
||||
try {
|
||||
await api.put("/api/admin/availability", {
|
||||
date: formatDate(selectedDate),
|
||||
slots: [],
|
||||
});
|
||||
|
||||
|
||||
await fetchAvailability();
|
||||
closeModal();
|
||||
} catch (err) {
|
||||
|
|
@ -186,16 +192,16 @@ export default function AdminAvailabilityPage() {
|
|||
|
||||
const executeCopy = async () => {
|
||||
if (!copySource || copyTargets.size === 0) return;
|
||||
|
||||
|
||||
setIsCopying(true);
|
||||
setError(null);
|
||||
|
||||
|
||||
try {
|
||||
await api.post("/api/admin/availability/copy", {
|
||||
source_date: copySource,
|
||||
target_dates: Array.from(copyTargets),
|
||||
});
|
||||
|
||||
|
||||
await fetchAvailability();
|
||||
cancelCopyMode();
|
||||
} catch (err) {
|
||||
|
|
@ -236,10 +242,12 @@ export default function AdminAvailabilityPage() {
|
|||
</div>
|
||||
{copySource && (
|
||||
<div style={styles.copyActions}>
|
||||
<span style={styles.copyHint}>
|
||||
Select days to copy to, then click Copy
|
||||
</span>
|
||||
<button onClick={executeCopy} disabled={copyTargets.size === 0 || isCopying} style={styles.copyButton}>
|
||||
<span style={styles.copyHint}>Select days to copy to, then click Copy</span>
|
||||
<button
|
||||
onClick={executeCopy}
|
||||
disabled={copyTargets.size === 0 || isCopying}
|
||||
style={styles.copyButton}
|
||||
>
|
||||
{isCopying ? "Copying..." : `Copy to ${copyTargets.size} day(s)`}
|
||||
</button>
|
||||
<button onClick={cancelCopyMode} style={styles.cancelButton}>
|
||||
|
|
@ -249,9 +257,7 @@ export default function AdminAvailabilityPage() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{error && !selectedDate && (
|
||||
<div style={styles.errorBanner}>{error}</div>
|
||||
)}
|
||||
{error && !selectedDate && <div style={styles.errorBanner}>{error}</div>}
|
||||
|
||||
<div style={styles.calendar}>
|
||||
{dates.map((date) => {
|
||||
|
|
@ -318,9 +324,7 @@ export default function AdminAvailabilityPage() {
|
|||
{selectedDate && (
|
||||
<div style={styles.modalOverlay} onClick={closeModal}>
|
||||
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
|
||||
<h2 style={styles.modalTitle}>
|
||||
Edit Availability - {formatDisplayDate(selectedDate)}
|
||||
</h2>
|
||||
<h2 style={styles.modalTitle}>Edit Availability - {formatDisplayDate(selectedDate)}</h2>
|
||||
|
||||
{error && <div style={styles.modalError}>{error}</div>}
|
||||
|
||||
|
|
@ -333,7 +337,9 @@ export default function AdminAvailabilityPage() {
|
|||
style={styles.timeSelect}
|
||||
>
|
||||
{TIME_OPTIONS.map((t) => (
|
||||
<option key={t} value={t}>{t}</option>
|
||||
<option key={t} value={t}>
|
||||
{t}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span style={styles.slotDash}>→</span>
|
||||
|
|
@ -343,7 +349,9 @@ export default function AdminAvailabilityPage() {
|
|||
style={styles.timeSelect}
|
||||
>
|
||||
{TIME_OPTIONS.map((t) => (
|
||||
<option key={t} value={t}>{t}</option>
|
||||
<option key={t} value={t}>
|
||||
{t}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
|
|
@ -629,4 +637,3 @@ const pageStyles: Record<string, React.CSSProperties> = {
|
|||
};
|
||||
|
||||
const styles = { ...sharedStyles, ...pageStyles };
|
||||
|
||||
|
|
|
|||
|
|
@ -66,10 +66,10 @@ export default function AdminInvitesPage() {
|
|||
setCreateError("Please select a godfather");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setIsCreating(true);
|
||||
setCreateError(null);
|
||||
|
||||
|
||||
try {
|
||||
await api.post("/api/admin/invites", {
|
||||
godfather_id: parseInt(newGodfatherId),
|
||||
|
|
@ -185,12 +185,10 @@ export default function AdminInvitesPage() {
|
|||
<option value={SPENT}>Spent</option>
|
||||
<option value={REVOKED}>Revoked</option>
|
||||
</select>
|
||||
<span style={styles.totalCount}>
|
||||
{data?.total ?? 0} invites
|
||||
</span>
|
||||
<span style={styles.totalCount}>{data?.total ?? 0} invites</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div style={styles.tableWrapper}>
|
||||
<table style={styles.table}>
|
||||
<thead>
|
||||
|
|
@ -206,43 +204,48 @@ export default function AdminInvitesPage() {
|
|||
<tbody>
|
||||
{error && (
|
||||
<tr>
|
||||
<td colSpan={6} style={styles.errorRow}>{error}</td>
|
||||
<td colSpan={6} style={styles.errorRow}>
|
||||
{error}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{!error && data?.records.map((record) => (
|
||||
<tr key={record.id} style={styles.tr}>
|
||||
<td style={styles.tdCode}>{record.identifier}</td>
|
||||
<td style={styles.td}>{record.godfather_email}</td>
|
||||
<td style={styles.td}>
|
||||
<span style={{ ...styles.statusBadge, ...getStatusBadgeStyle(record.status) }}>
|
||||
{record.status}
|
||||
</span>
|
||||
</td>
|
||||
<td style={styles.td}>
|
||||
{record.used_by_email || "-"}
|
||||
</td>
|
||||
<td style={styles.tdDate}>{formatDate(record.created_at)}</td>
|
||||
<td style={styles.td}>
|
||||
{record.status === READY && (
|
||||
<button
|
||||
onClick={() => handleRevoke(record.id)}
|
||||
style={styles.revokeButton}
|
||||
{!error &&
|
||||
data?.records.map((record) => (
|
||||
<tr key={record.id} style={styles.tr}>
|
||||
<td style={styles.tdCode}>{record.identifier}</td>
|
||||
<td style={styles.td}>{record.godfather_email}</td>
|
||||
<td style={styles.td}>
|
||||
<span
|
||||
style={{ ...styles.statusBadge, ...getStatusBadgeStyle(record.status) }}
|
||||
>
|
||||
Revoke
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{record.status}
|
||||
</span>
|
||||
</td>
|
||||
<td style={styles.td}>{record.used_by_email || "-"}</td>
|
||||
<td style={styles.tdDate}>{formatDate(record.created_at)}</td>
|
||||
<td style={styles.td}>
|
||||
{record.status === READY && (
|
||||
<button
|
||||
onClick={() => handleRevoke(record.id)}
|
||||
style={styles.revokeButton}
|
||||
>
|
||||
Revoke
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{!error && (!data || data.records.length === 0) && (
|
||||
<tr>
|
||||
<td colSpan={6} style={styles.emptyRow}>No invites yet</td>
|
||||
<td colSpan={6} style={styles.emptyRow}>
|
||||
No invites yet
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
{data && data.total_pages > 1 && (
|
||||
<div style={styles.pagination}>
|
||||
<button
|
||||
|
|
@ -500,4 +503,3 @@ const pageStyles: Record<string, React.CSSProperties> = {
|
|||
};
|
||||
|
||||
const styles = { ...sharedStyles, ...pageStyles };
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue