Phase 6: Admin appointments view and cancellation with UI and backend tests
This commit is contained in:
parent
5108a620e7
commit
b3e00b0745
12 changed files with 814 additions and 548 deletions
|
|
@ -49,4 +49,5 @@ app.include_router(invites_routes.admin_router)
|
|||
app.include_router(availability_routes.router)
|
||||
app.include_router(booking_routes.router)
|
||||
app.include_router(booking_routes.appointments_router)
|
||||
app.include_router(booking_routes.admin_appointments_router)
|
||||
app.include_router(meta_routes.router)
|
||||
|
|
|
|||
|
|
@ -203,6 +203,13 @@ async def create_booking(
|
|||
appointments_router = APIRouter(prefix="/api/appointments", tags=["appointments"])
|
||||
|
||||
|
||||
async def _get_user_email(db: AsyncSession, user_id: int) -> str:
|
||||
"""Get user email by ID."""
|
||||
result = await db.execute(select(User.email).where(User.id == user_id))
|
||||
email = result.scalar_one_or_none()
|
||||
return email or "unknown"
|
||||
|
||||
|
||||
@appointments_router.get("", response_model=list[AppointmentResponse])
|
||||
async def get_my_appointments(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
|
|
@ -278,3 +285,84 @@ async def cancel_my_appointment(
|
|||
cancelled_at=appointment.cancelled_at,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Admin Appointments Endpoints
|
||||
# =============================================================================
|
||||
|
||||
admin_appointments_router = APIRouter(prefix="/api/admin/appointments", tags=["admin-appointments"])
|
||||
|
||||
|
||||
@admin_appointments_router.get("", response_model=list[AppointmentResponse])
|
||||
async def get_all_appointments(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_current_user: User = Depends(require_permission(Permission.VIEW_ALL_APPOINTMENTS)),
|
||||
) -> list[AppointmentResponse]:
|
||||
"""Get all appointments (admin only), sorted by date descending."""
|
||||
result = await db.execute(
|
||||
select(Appointment)
|
||||
.order_by(Appointment.slot_start.desc())
|
||||
)
|
||||
appointments = result.scalars().all()
|
||||
|
||||
responses = []
|
||||
for apt in appointments:
|
||||
user_email = await _get_user_email(db, apt.user_id)
|
||||
responses.append(AppointmentResponse(
|
||||
id=apt.id,
|
||||
user_id=apt.user_id,
|
||||
user_email=user_email,
|
||||
slot_start=apt.slot_start,
|
||||
slot_end=apt.slot_end,
|
||||
note=apt.note,
|
||||
status=apt.status.value,
|
||||
created_at=apt.created_at,
|
||||
cancelled_at=apt.cancelled_at,
|
||||
))
|
||||
|
||||
return responses
|
||||
|
||||
|
||||
@admin_appointments_router.post("/{appointment_id}/cancel", response_model=AppointmentResponse)
|
||||
async def admin_cancel_appointment(
|
||||
appointment_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_current_user: User = Depends(require_permission(Permission.CANCEL_ANY_APPOINTMENT)),
|
||||
) -> AppointmentResponse:
|
||||
"""Cancel any appointment (admin only)."""
|
||||
# Get the appointment
|
||||
result = await db.execute(
|
||||
select(Appointment).where(Appointment.id == appointment_id)
|
||||
)
|
||||
appointment = result.scalar_one_or_none()
|
||||
|
||||
if not appointment:
|
||||
raise HTTPException(status_code=404, detail="Appointment not found")
|
||||
|
||||
# Check if already cancelled
|
||||
if appointment.status != AppointmentStatus.BOOKED:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Cannot cancel appointment with status '{appointment.status.value}'"
|
||||
)
|
||||
|
||||
# Cancel the appointment
|
||||
appointment.status = AppointmentStatus.CANCELLED_BY_ADMIN
|
||||
appointment.cancelled_at = datetime.now(timezone.utc)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(appointment)
|
||||
|
||||
user_email = await _get_user_email(db, appointment.user_id)
|
||||
|
||||
return AppointmentResponse(
|
||||
id=appointment.id,
|
||||
user_id=appointment.user_id,
|
||||
user_email=user_email,
|
||||
slot_start=appointment.slot_start,
|
||||
slot_end=appointment.slot_end,
|
||||
note=appointment.note,
|
||||
status=appointment.status.value,
|
||||
created_at=appointment.created_at,
|
||||
cancelled_at=appointment.cancelled_at,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -656,3 +656,153 @@ class TestCancelAppointment:
|
|||
)
|
||||
assert len(slots_response.json()["slots"]) == 2
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Admin Appointments Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestAdminViewAppointments:
|
||||
"""Test admin viewing all appointments."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_can_view_all_appointments(self, client_factory, regular_user, admin_user):
|
||||
"""Admin can view all appointments."""
|
||||
# Admin sets availability
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as admin_client:
|
||||
await admin_client.put(
|
||||
"/api/admin/availability",
|
||||
json={
|
||||
"date": str(tomorrow()),
|
||||
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
|
||||
},
|
||||
)
|
||||
|
||||
# User books
|
||||
async with client_factory.create(cookies=regular_user["cookies"]) as client:
|
||||
await client.post(
|
||||
"/api/booking",
|
||||
json={"slot_start": f"{tomorrow()}T09:00:00Z", "note": "Test"},
|
||||
)
|
||||
|
||||
# Admin views all appointments
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as admin_client:
|
||||
response = await admin_client.get("/api/admin/appointments")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) >= 1
|
||||
assert any(apt["note"] == "Test" for apt in data)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_regular_user_cannot_view_all_appointments(self, client_factory, regular_user):
|
||||
"""Regular user cannot access admin appointments endpoint."""
|
||||
async with client_factory.create(cookies=regular_user["cookies"]) as client:
|
||||
response = await client.get("/api/admin/appointments")
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unauthenticated_cannot_view_all_appointments(self, client):
|
||||
"""Unauthenticated user cannot view appointments."""
|
||||
response = await client.get("/api/admin/appointments")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
class TestAdminCancelAppointment:
|
||||
"""Test admin cancelling appointments."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_can_cancel_any_appointment(self, client_factory, regular_user, admin_user):
|
||||
"""Admin can cancel any user's appointment."""
|
||||
# Admin sets availability
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as admin_client:
|
||||
await admin_client.put(
|
||||
"/api/admin/availability",
|
||||
json={
|
||||
"date": str(tomorrow()),
|
||||
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
|
||||
},
|
||||
)
|
||||
|
||||
# User books
|
||||
async with client_factory.create(cookies=regular_user["cookies"]) as client:
|
||||
book_response = await client.post(
|
||||
"/api/booking",
|
||||
json={"slot_start": f"{tomorrow()}T09:00:00Z"},
|
||||
)
|
||||
apt_id = book_response.json()["id"]
|
||||
|
||||
# Admin cancels
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as admin_client:
|
||||
response = await admin_client.post(f"/api/admin/appointments/{apt_id}/cancel")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "cancelled_by_admin"
|
||||
assert data["cancelled_at"] is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_regular_user_cannot_use_admin_cancel(self, client_factory, regular_user, admin_user):
|
||||
"""Regular user cannot use admin cancel endpoint."""
|
||||
# Admin sets availability
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as admin_client:
|
||||
await admin_client.put(
|
||||
"/api/admin/availability",
|
||||
json={
|
||||
"date": str(tomorrow()),
|
||||
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
|
||||
},
|
||||
)
|
||||
|
||||
# User books
|
||||
async with client_factory.create(cookies=regular_user["cookies"]) as client:
|
||||
book_response = await client.post(
|
||||
"/api/booking",
|
||||
json={"slot_start": f"{tomorrow()}T09:00:00Z"},
|
||||
)
|
||||
apt_id = book_response.json()["id"]
|
||||
|
||||
# User tries to use admin cancel endpoint
|
||||
response = await client.post(f"/api/admin/appointments/{apt_id}/cancel")
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_cancel_nonexistent_appointment(self, client_factory, admin_user):
|
||||
"""Returns 404 for non-existent appointment."""
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
response = await client.post("/api/admin/appointments/99999/cancel")
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_cannot_cancel_already_cancelled(self, client_factory, regular_user, admin_user):
|
||||
"""Admin cannot cancel an already cancelled appointment."""
|
||||
# Admin sets availability
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as admin_client:
|
||||
await admin_client.put(
|
||||
"/api/admin/availability",
|
||||
json={
|
||||
"date": str(tomorrow()),
|
||||
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
|
||||
},
|
||||
)
|
||||
|
||||
# User books
|
||||
async with client_factory.create(cookies=regular_user["cookies"]) as client:
|
||||
book_response = await client.post(
|
||||
"/api/booking",
|
||||
json={"slot_start": f"{tomorrow()}T09:00:00Z"},
|
||||
)
|
||||
apt_id = book_response.json()["id"]
|
||||
|
||||
# User cancels their own appointment
|
||||
await client.post(f"/api/appointments/{apt_id}/cancel")
|
||||
|
||||
# Admin tries to cancel again
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as admin_client:
|
||||
response = await admin_client.post(f"/api/admin/appointments/{apt_id}/cancel")
|
||||
|
||||
assert response.status_code == 400
|
||||
assert "cancelled_by_user" in response.json()["detail"]
|
||||
|
||||
|
|
|
|||
259
frontend/app/admin/appointments/page.tsx
Normal file
259
frontend/app/admin/appointments/page.tsx
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { Permission } from "../../auth-context";
|
||||
import { api } from "../../api";
|
||||
import { sharedStyles } from "../../styles/shared";
|
||||
import { Header } from "../../components/Header";
|
||||
import { useRequireAuth } from "../../hooks/useRequireAuth";
|
||||
import { components } from "../../generated/api";
|
||||
|
||||
type AppointmentResponse = components["schemas"]["AppointmentResponse"];
|
||||
|
||||
// Helper to format datetime
|
||||
function formatDateTime(isoString: string): string {
|
||||
const d = new Date(isoString);
|
||||
return d.toLocaleString("en-US", {
|
||||
weekday: "short",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper to get status display
|
||||
function getStatusDisplay(status: string): { text: string; color: string } {
|
||||
switch (status) {
|
||||
case "booked":
|
||||
return { text: "Booked", color: "#28a745" };
|
||||
case "cancelled_by_user":
|
||||
return { text: "Cancelled by user", color: "#dc3545" };
|
||||
case "cancelled_by_admin":
|
||||
return { text: "Cancelled by admin", color: "#dc3545" };
|
||||
default:
|
||||
return { text: status, color: "#666" };
|
||||
}
|
||||
}
|
||||
|
||||
export default function AdminAppointmentsPage() {
|
||||
const { user, isLoading, isAuthorized } = useRequireAuth({
|
||||
requiredPermission: Permission.VIEW_ALL_APPOINTMENTS,
|
||||
fallbackRedirect: "/",
|
||||
});
|
||||
|
||||
const [appointments, setAppointments] = useState<AppointmentResponse[]>([]);
|
||||
const [isLoadingAppointments, setIsLoadingAppointments] = useState(true);
|
||||
const [cancellingId, setCancellingId] = useState<number | null>(null);
|
||||
const [confirmCancelId, setConfirmCancelId] = useState<number | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [statusFilter, setStatusFilter] = useState<string>("all");
|
||||
|
||||
const fetchAppointments = useCallback(async () => {
|
||||
try {
|
||||
const data = await api.get<AppointmentResponse[]>("/api/admin/appointments");
|
||||
setAppointments(data);
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch appointments:", err);
|
||||
setError("Failed to load appointments");
|
||||
} finally {
|
||||
setIsLoadingAppointments(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (user && isAuthorized) {
|
||||
fetchAppointments();
|
||||
}
|
||||
}, [user, isAuthorized, fetchAppointments]);
|
||||
|
||||
const handleCancel = async (appointmentId: number) => {
|
||||
setCancellingId(appointmentId);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await api.post<AppointmentResponse>(`/api/admin/appointments/${appointmentId}/cancel`, {});
|
||||
await fetchAppointments();
|
||||
setConfirmCancelId(null);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
setError(err.message);
|
||||
} else {
|
||||
setError("Failed to cancel appointment");
|
||||
}
|
||||
} finally {
|
||||
setCancellingId(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div style={sharedStyles.pageContainer}>
|
||||
<Header currentPage="admin-appointments" />
|
||||
<main style={sharedStyles.mainContent}>
|
||||
<p>Loading...</p>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAuthorized) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filteredAppointments = appointments.filter((apt) => {
|
||||
if (statusFilter === "all") return true;
|
||||
return apt.status === statusFilter;
|
||||
});
|
||||
|
||||
const bookedCount = appointments.filter((a) => a.status === "booked").length;
|
||||
const cancelledCount = appointments.filter((a) => a.status !== "booked").length;
|
||||
|
||||
return (
|
||||
<div style={sharedStyles.pageContainer}>
|
||||
<Header currentPage="admin-appointments" />
|
||||
<main style={sharedStyles.mainContent}>
|
||||
<h1 style={{ marginBottom: "0.5rem" }}>All Appointments</h1>
|
||||
<p style={{ color: "#666", marginBottom: "1.5rem" }}>
|
||||
View and manage all user appointments
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div style={{
|
||||
background: "#f8d7da",
|
||||
border: "1px solid #f5c6cb",
|
||||
color: "#721c24",
|
||||
padding: "1rem",
|
||||
borderRadius: "8px",
|
||||
marginBottom: "1rem",
|
||||
}}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Status Filter */}
|
||||
<div style={{ marginBottom: "1rem", display: "flex", gap: "0.5rem", alignItems: "center" }}>
|
||||
<label style={{ fontWeight: 500 }}>Filter:</label>
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value)}
|
||||
style={{
|
||||
padding: "0.5rem",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<option value="all">All ({appointments.length})</option>
|
||||
<option value="booked">Booked ({bookedCount})</option>
|
||||
<option value="cancelled_by_user">Cancelled by User</option>
|
||||
<option value="cancelled_by_admin">Cancelled by Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isLoadingAppointments ? (
|
||||
<p>Loading appointments...</p>
|
||||
) : appointments.length === 0 ? (
|
||||
<p style={{ color: "#666" }}>No appointments yet.</p>
|
||||
) : filteredAppointments.length === 0 ? (
|
||||
<p style={{ color: "#666" }}>No appointments match the filter.</p>
|
||||
) : (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
|
||||
{filteredAppointments.map((apt) => {
|
||||
const status = getStatusDisplay(apt.status);
|
||||
const isPast = new Date(apt.slot_start) <= new Date();
|
||||
return (
|
||||
<div
|
||||
key={apt.id}
|
||||
style={{
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "8px",
|
||||
padding: "1rem",
|
||||
background: isPast ? "#fafafa" : "#fff",
|
||||
opacity: isPast && apt.status !== "booked" ? 0.7 : 1,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500, marginBottom: "0.25rem" }}>
|
||||
{formatDateTime(apt.slot_start)}
|
||||
</div>
|
||||
<div style={{ color: "#666", fontSize: "0.875rem", marginBottom: "0.25rem" }}>
|
||||
User: {apt.user_email}
|
||||
</div>
|
||||
{apt.note && (
|
||||
<div style={{ color: "#888", fontSize: "0.875rem", marginBottom: "0.5rem", fontStyle: "italic" }}>
|
||||
"{apt.note}"
|
||||
</div>
|
||||
)}
|
||||
<span style={{
|
||||
color: status.color,
|
||||
fontSize: "0.75rem",
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
{status.text}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{apt.status === "booked" && (
|
||||
<div>
|
||||
{confirmCancelId === apt.id ? (
|
||||
<div style={{ display: "flex", gap: "0.5rem" }}>
|
||||
<button
|
||||
onClick={() => handleCancel(apt.id)}
|
||||
disabled={cancellingId === apt.id}
|
||||
style={{
|
||||
padding: "0.25rem 0.75rem",
|
||||
background: "#dc3545",
|
||||
color: "#fff",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
cursor: cancellingId === apt.id ? "not-allowed" : "pointer",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{cancellingId === apt.id ? "..." : "Confirm"}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setConfirmCancelId(null)}
|
||||
style={{
|
||||
padding: "0.25rem 0.75rem",
|
||||
background: "#fff",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
No
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setConfirmCancelId(apt.id)}
|
||||
style={{
|
||||
padding: "0.25rem 0.75rem",
|
||||
background: "#fff",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
fontSize: "0.75rem",
|
||||
color: "#666",
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ import constants from "../../../shared/constants.json";
|
|||
|
||||
const { ADMIN, REGULAR } = constants.roles;
|
||||
|
||||
type PageId = "counter" | "sum" | "profile" | "invites" | "booking" | "appointments" | "audit" | "admin-invites" | "admin-availability";
|
||||
type PageId = "counter" | "sum" | "profile" | "invites" | "booking" | "appointments" | "audit" | "admin-invites" | "admin-availability" | "admin-appointments";
|
||||
|
||||
interface HeaderProps {
|
||||
currentPage: PageId;
|
||||
|
|
@ -34,6 +34,7 @@ const ADMIN_NAV_ITEMS: NavItem[] = [
|
|||
{ id: "audit", label: "Audit", href: "/audit", adminOnly: true },
|
||||
{ id: "admin-invites", label: "Invites", href: "/admin/invites", adminOnly: true },
|
||||
{ id: "admin-availability", label: "Availability", href: "/admin/availability", adminOnly: true },
|
||||
{ id: "admin-appointments", label: "Appointments", href: "/admin/appointments", adminOnly: true },
|
||||
];
|
||||
|
||||
export function Header({ currentPage }: HeaderProps) {
|
||||
|
|
|
|||
|
|
@ -436,6 +436,46 @@ export interface paths {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/admin/appointments": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Get All Appointments
|
||||
* @description Get all appointments (admin only), sorted by date descending.
|
||||
*/
|
||||
get: operations["get_all_appointments_api_admin_appointments_get"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/admin/appointments/{appointment_id}/cancel": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Admin Cancel Appointment
|
||||
* @description Cancel any appointment (admin only).
|
||||
*/
|
||||
post: operations["admin_cancel_appointment_api_admin_appointments__appointment_id__cancel_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/meta/constants": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
|
@ -1575,6 +1615,57 @@ export interface operations {
|
|||
};
|
||||
};
|
||||
};
|
||||
get_all_appointments_api_admin_appointments_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["AppointmentResponse"][];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
admin_cancel_appointment_api_admin_appointments__appointment_id__cancel_post: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
appointment_id: number;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["AppointmentResponse"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
get_constants_api_meta_constants_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
"status": "failed",
|
||||
"failedTests": [
|
||||
"647d672ac99574a52088-7123e0baf27b194c0b82",
|
||||
"647d672ac99574a52088-a2e9f69e9c9ef92dc2bb",
|
||||
"647d672ac99574a52088-f95d3555fba5395917f0",
|
||||
"a3a10f2351dcc49e1cb9-ac24d656b0f11d39342d",
|
||||
"50879ed375f8988ef978-7be9cdef76510785fb98"
|
||||
"647d672ac99574a52088-a2e9f69e9c9ef92dc2bb"
|
||||
]
|
||||
}
|
||||
|
|
@ -12,112 +12,116 @@
|
|||
- link "Invites" [ref=e8] [cursor=pointer]:
|
||||
- /url: /admin/invites
|
||||
- generic [ref=e9]: •Availability
|
||||
- generic [ref=e10]:
|
||||
- generic [ref=e11]: admin@example.com
|
||||
- button "Sign out" [ref=e12] [cursor=pointer]
|
||||
- generic [ref=e14]:
|
||||
- generic [ref=e16]:
|
||||
- heading "Availability" [level=1] [ref=e17]
|
||||
- paragraph [ref=e18]: Configure your available time slots for the next 30 days
|
||||
- generic [ref=e19]:
|
||||
- generic [ref=e20] [cursor=pointer]:
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22]: Mon, Dec 22
|
||||
- button "⎘" [ref=e23]
|
||||
- generic [ref=e25]: 09:00 - 12:00
|
||||
- generic [ref=e26] [cursor=pointer]:
|
||||
- generic [ref=e28]: Tue, Dec 23
|
||||
- generic [ref=e30]: No availability
|
||||
- generic [ref=e31] [cursor=pointer]:
|
||||
- generic [ref=e33]: Wed, Dec 24
|
||||
- generic [ref=e35]: No availability
|
||||
- generic [ref=e36] [cursor=pointer]:
|
||||
- generic [ref=e38]: Thu, Dec 25
|
||||
- generic [ref=e40]: No availability
|
||||
- generic [ref=e41] [cursor=pointer]:
|
||||
- generic [ref=e43]: Fri, Dec 26
|
||||
- generic [ref=e45]: No availability
|
||||
- generic [ref=e46] [cursor=pointer]:
|
||||
- generic [ref=e48]: Sat, Dec 27
|
||||
- generic [ref=e50]: No availability
|
||||
- generic [ref=e51] [cursor=pointer]:
|
||||
- generic [ref=e53]: Sun, Dec 28
|
||||
- generic [ref=e55]: No availability
|
||||
- generic [ref=e56] [cursor=pointer]:
|
||||
- generic [ref=e58]: Mon, Dec 29
|
||||
- generic [ref=e60]: No availability
|
||||
- generic [ref=e61] [cursor=pointer]:
|
||||
- generic [ref=e63]: Tue, Dec 30
|
||||
- generic [ref=e65]: No availability
|
||||
- generic [ref=e66] [cursor=pointer]:
|
||||
- generic [ref=e68]: Wed, Dec 31
|
||||
- generic [ref=e70]: No availability
|
||||
- generic [ref=e71] [cursor=pointer]:
|
||||
- generic [ref=e73]: Thu, Jan 1
|
||||
- generic [ref=e75]: No availability
|
||||
- generic [ref=e76] [cursor=pointer]:
|
||||
- generic [ref=e78]: Fri, Jan 2
|
||||
- generic [ref=e80]: No availability
|
||||
- generic [ref=e81] [cursor=pointer]:
|
||||
- generic [ref=e83]: Sat, Jan 3
|
||||
- generic [ref=e85]: No availability
|
||||
- generic [ref=e86] [cursor=pointer]:
|
||||
- generic [ref=e88]: Sun, Jan 4
|
||||
- generic [ref=e90]: No availability
|
||||
- generic [ref=e91] [cursor=pointer]:
|
||||
- generic [ref=e93]: Mon, Jan 5
|
||||
- generic [ref=e95]: No availability
|
||||
- generic [ref=e96] [cursor=pointer]:
|
||||
- generic [ref=e98]: Tue, Jan 6
|
||||
- generic [ref=e100]: No availability
|
||||
- generic [ref=e101] [cursor=pointer]:
|
||||
- generic [ref=e103]: Wed, Jan 7
|
||||
- generic [ref=e105]: No availability
|
||||
- generic [ref=e106] [cursor=pointer]:
|
||||
- generic [ref=e108]: Thu, Jan 8
|
||||
- generic [ref=e110]: No availability
|
||||
- generic [ref=e111] [cursor=pointer]:
|
||||
- generic [ref=e113]: Fri, Jan 9
|
||||
- generic [ref=e115]: No availability
|
||||
- generic [ref=e116] [cursor=pointer]:
|
||||
- generic [ref=e118]: Sat, Jan 10
|
||||
- generic [ref=e120]: No availability
|
||||
- generic [ref=e121] [cursor=pointer]:
|
||||
- generic [ref=e123]: Sun, Jan 11
|
||||
- generic [ref=e125]: No availability
|
||||
- generic [ref=e126] [cursor=pointer]:
|
||||
- generic [ref=e128]: Mon, Jan 12
|
||||
- generic [ref=e130]: No availability
|
||||
- generic [ref=e131] [cursor=pointer]:
|
||||
- generic [ref=e133]: Tue, Jan 13
|
||||
- generic [ref=e135]: No availability
|
||||
- generic [ref=e136] [cursor=pointer]:
|
||||
- generic [ref=e138]: Wed, Jan 14
|
||||
- generic [ref=e140]: No availability
|
||||
- generic [ref=e141] [cursor=pointer]:
|
||||
- generic [ref=e143]: Thu, Jan 15
|
||||
- generic [ref=e145]: No availability
|
||||
- generic [ref=e146] [cursor=pointer]:
|
||||
- generic [ref=e148]: Fri, Jan 16
|
||||
- generic [ref=e150]: No availability
|
||||
- generic [ref=e151] [cursor=pointer]:
|
||||
- generic [ref=e153]: Sat, Jan 17
|
||||
- generic [ref=e155]: No availability
|
||||
- generic [ref=e156] [cursor=pointer]:
|
||||
- generic [ref=e158]: Sun, Jan 18
|
||||
- generic [ref=e160]: No availability
|
||||
- generic [ref=e161] [cursor=pointer]:
|
||||
- generic [ref=e163]: Mon, Jan 19
|
||||
- generic [ref=e165]: No availability
|
||||
- generic [ref=e166] [cursor=pointer]:
|
||||
- generic [ref=e168]: Tue, Jan 20
|
||||
- generic [ref=e170]: No availability
|
||||
- status [ref=e171]:
|
||||
- generic [ref=e172]:
|
||||
- img [ref=e174]
|
||||
- generic [ref=e176]:
|
||||
- generic [ref=e10]:
|
||||
- text: •
|
||||
- link "Appointments" [ref=e11] [cursor=pointer]:
|
||||
- /url: /admin/appointments
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]: admin@example.com
|
||||
- button "Sign out" [ref=e14] [cursor=pointer]
|
||||
- generic [ref=e16]:
|
||||
- generic [ref=e18]:
|
||||
- heading "Availability" [level=1] [ref=e19]
|
||||
- paragraph [ref=e20]: Configure your available time slots for the next 30 days
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22] [cursor=pointer]:
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24]: Mon, Dec 22
|
||||
- button "⎘" [ref=e25]
|
||||
- generic [ref=e27]: 09:00 - 12:00
|
||||
- generic [ref=e28] [cursor=pointer]:
|
||||
- generic [ref=e30]: Tue, Dec 23
|
||||
- generic [ref=e32]: No availability
|
||||
- generic [ref=e33] [cursor=pointer]:
|
||||
- generic [ref=e35]: Wed, Dec 24
|
||||
- generic [ref=e37]: No availability
|
||||
- generic [ref=e38] [cursor=pointer]:
|
||||
- generic [ref=e40]: Thu, Dec 25
|
||||
- generic [ref=e42]: No availability
|
||||
- generic [ref=e43] [cursor=pointer]:
|
||||
- generic [ref=e45]: Fri, Dec 26
|
||||
- generic [ref=e47]: No availability
|
||||
- generic [ref=e48] [cursor=pointer]:
|
||||
- generic [ref=e50]: Sat, Dec 27
|
||||
- generic [ref=e52]: No availability
|
||||
- generic [ref=e53] [cursor=pointer]:
|
||||
- generic [ref=e55]: Sun, Dec 28
|
||||
- generic [ref=e57]: No availability
|
||||
- generic [ref=e58] [cursor=pointer]:
|
||||
- generic [ref=e60]: Mon, Dec 29
|
||||
- generic [ref=e62]: No availability
|
||||
- generic [ref=e63] [cursor=pointer]:
|
||||
- generic [ref=e65]: Tue, Dec 30
|
||||
- generic [ref=e67]: No availability
|
||||
- generic [ref=e68] [cursor=pointer]:
|
||||
- generic [ref=e70]: Wed, Dec 31
|
||||
- generic [ref=e72]: No availability
|
||||
- generic [ref=e73] [cursor=pointer]:
|
||||
- generic [ref=e75]: Thu, Jan 1
|
||||
- generic [ref=e77]: No availability
|
||||
- generic [ref=e78] [cursor=pointer]:
|
||||
- generic [ref=e80]: Fri, Jan 2
|
||||
- generic [ref=e82]: No availability
|
||||
- generic [ref=e83] [cursor=pointer]:
|
||||
- generic [ref=e85]: Sat, Jan 3
|
||||
- generic [ref=e87]: No availability
|
||||
- generic [ref=e88] [cursor=pointer]:
|
||||
- generic [ref=e90]: Sun, Jan 4
|
||||
- generic [ref=e92]: No availability
|
||||
- generic [ref=e93] [cursor=pointer]:
|
||||
- generic [ref=e95]: Mon, Jan 5
|
||||
- generic [ref=e97]: No availability
|
||||
- generic [ref=e98] [cursor=pointer]:
|
||||
- generic [ref=e100]: Tue, Jan 6
|
||||
- generic [ref=e102]: No availability
|
||||
- generic [ref=e103] [cursor=pointer]:
|
||||
- generic [ref=e105]: Wed, Jan 7
|
||||
- generic [ref=e107]: No availability
|
||||
- generic [ref=e108] [cursor=pointer]:
|
||||
- generic [ref=e110]: Thu, Jan 8
|
||||
- generic [ref=e112]: No availability
|
||||
- generic [ref=e113] [cursor=pointer]:
|
||||
- generic [ref=e115]: Fri, Jan 9
|
||||
- generic [ref=e117]: No availability
|
||||
- generic [ref=e118] [cursor=pointer]:
|
||||
- generic [ref=e120]: Sat, Jan 10
|
||||
- generic [ref=e122]: No availability
|
||||
- generic [ref=e123] [cursor=pointer]:
|
||||
- generic [ref=e125]: Sun, Jan 11
|
||||
- generic [ref=e127]: No availability
|
||||
- generic [ref=e128] [cursor=pointer]:
|
||||
- generic [ref=e130]: Mon, Jan 12
|
||||
- generic [ref=e132]: No availability
|
||||
- generic [ref=e133] [cursor=pointer]:
|
||||
- generic [ref=e135]: Tue, Jan 13
|
||||
- generic [ref=e137]: No availability
|
||||
- generic [ref=e138] [cursor=pointer]:
|
||||
- generic [ref=e140]: Wed, Jan 14
|
||||
- generic [ref=e142]: No availability
|
||||
- generic [ref=e143] [cursor=pointer]:
|
||||
- generic [ref=e145]: Thu, Jan 15
|
||||
- generic [ref=e147]: No availability
|
||||
- generic [ref=e148] [cursor=pointer]:
|
||||
- generic [ref=e150]: Fri, Jan 16
|
||||
- generic [ref=e152]: No availability
|
||||
- generic [ref=e153] [cursor=pointer]:
|
||||
- generic [ref=e155]: Sat, Jan 17
|
||||
- generic [ref=e157]: No availability
|
||||
- generic [ref=e158] [cursor=pointer]:
|
||||
- generic [ref=e160]: Sun, Jan 18
|
||||
- generic [ref=e162]: No availability
|
||||
- generic [ref=e163] [cursor=pointer]:
|
||||
- generic [ref=e165]: Mon, Jan 19
|
||||
- generic [ref=e167]: No availability
|
||||
- generic [ref=e168] [cursor=pointer]:
|
||||
- generic [ref=e170]: Tue, Jan 20
|
||||
- generic [ref=e172]: No availability
|
||||
- status [ref=e173]:
|
||||
- generic [ref=e174]:
|
||||
- img [ref=e176]
|
||||
- generic [ref=e178]:
|
||||
- text: Static route
|
||||
- button "Hide static indicator" [ref=e177] [cursor=pointer]:
|
||||
- img [ref=e178]
|
||||
- alert [ref=e181]
|
||||
- button "Hide static indicator" [ref=e179] [cursor=pointer]:
|
||||
- img [ref=e180]
|
||||
- alert [ref=e183]
|
||||
```
|
||||
|
|
@ -12,112 +12,116 @@
|
|||
- link "Invites" [ref=e8] [cursor=pointer]:
|
||||
- /url: /admin/invites
|
||||
- generic [ref=e9]: •Availability
|
||||
- generic [ref=e10]:
|
||||
- generic [ref=e11]: admin@example.com
|
||||
- button "Sign out" [ref=e12] [cursor=pointer]
|
||||
- generic [ref=e14]:
|
||||
- generic [ref=e16]:
|
||||
- heading "Availability" [level=1] [ref=e17]
|
||||
- paragraph [ref=e18]: Configure your available time slots for the next 30 days
|
||||
- generic [ref=e19]:
|
||||
- generic [ref=e20] [cursor=pointer]:
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22]: Mon, Dec 22
|
||||
- button "⎘" [ref=e23]
|
||||
- generic [ref=e25]: 09:00 - 12:00
|
||||
- generic [ref=e26] [cursor=pointer]:
|
||||
- generic [ref=e28]: Tue, Dec 23
|
||||
- generic [ref=e30]: No availability
|
||||
- generic [ref=e31] [cursor=pointer]:
|
||||
- generic [ref=e33]: Wed, Dec 24
|
||||
- generic [ref=e35]: No availability
|
||||
- generic [ref=e36] [cursor=pointer]:
|
||||
- generic [ref=e38]: Thu, Dec 25
|
||||
- generic [ref=e40]: No availability
|
||||
- generic [ref=e41] [cursor=pointer]:
|
||||
- generic [ref=e43]: Fri, Dec 26
|
||||
- generic [ref=e45]: No availability
|
||||
- generic [ref=e46] [cursor=pointer]:
|
||||
- generic [ref=e48]: Sat, Dec 27
|
||||
- generic [ref=e50]: No availability
|
||||
- generic [ref=e51] [cursor=pointer]:
|
||||
- generic [ref=e53]: Sun, Dec 28
|
||||
- generic [ref=e55]: No availability
|
||||
- generic [ref=e56] [cursor=pointer]:
|
||||
- generic [ref=e58]: Mon, Dec 29
|
||||
- generic [ref=e60]: No availability
|
||||
- generic [ref=e61] [cursor=pointer]:
|
||||
- generic [ref=e63]: Tue, Dec 30
|
||||
- generic [ref=e65]: No availability
|
||||
- generic [ref=e66] [cursor=pointer]:
|
||||
- generic [ref=e68]: Wed, Dec 31
|
||||
- generic [ref=e70]: No availability
|
||||
- generic [ref=e71] [cursor=pointer]:
|
||||
- generic [ref=e73]: Thu, Jan 1
|
||||
- generic [ref=e75]: No availability
|
||||
- generic [ref=e76] [cursor=pointer]:
|
||||
- generic [ref=e78]: Fri, Jan 2
|
||||
- generic [ref=e80]: No availability
|
||||
- generic [ref=e81] [cursor=pointer]:
|
||||
- generic [ref=e83]: Sat, Jan 3
|
||||
- generic [ref=e85]: No availability
|
||||
- generic [ref=e86] [cursor=pointer]:
|
||||
- generic [ref=e88]: Sun, Jan 4
|
||||
- generic [ref=e90]: No availability
|
||||
- generic [ref=e91] [cursor=pointer]:
|
||||
- generic [ref=e93]: Mon, Jan 5
|
||||
- generic [ref=e95]: No availability
|
||||
- generic [ref=e96] [cursor=pointer]:
|
||||
- generic [ref=e98]: Tue, Jan 6
|
||||
- generic [ref=e100]: No availability
|
||||
- generic [ref=e101] [cursor=pointer]:
|
||||
- generic [ref=e103]: Wed, Jan 7
|
||||
- generic [ref=e105]: No availability
|
||||
- generic [ref=e106] [cursor=pointer]:
|
||||
- generic [ref=e108]: Thu, Jan 8
|
||||
- generic [ref=e110]: No availability
|
||||
- generic [ref=e111] [cursor=pointer]:
|
||||
- generic [ref=e113]: Fri, Jan 9
|
||||
- generic [ref=e115]: No availability
|
||||
- generic [ref=e116] [cursor=pointer]:
|
||||
- generic [ref=e118]: Sat, Jan 10
|
||||
- generic [ref=e120]: No availability
|
||||
- generic [ref=e121] [cursor=pointer]:
|
||||
- generic [ref=e123]: Sun, Jan 11
|
||||
- generic [ref=e125]: No availability
|
||||
- generic [ref=e126] [cursor=pointer]:
|
||||
- generic [ref=e128]: Mon, Jan 12
|
||||
- generic [ref=e130]: No availability
|
||||
- generic [ref=e131] [cursor=pointer]:
|
||||
- generic [ref=e133]: Tue, Jan 13
|
||||
- generic [ref=e135]: No availability
|
||||
- generic [ref=e136] [cursor=pointer]:
|
||||
- generic [ref=e138]: Wed, Jan 14
|
||||
- generic [ref=e140]: No availability
|
||||
- generic [ref=e141] [cursor=pointer]:
|
||||
- generic [ref=e143]: Thu, Jan 15
|
||||
- generic [ref=e145]: No availability
|
||||
- generic [ref=e146] [cursor=pointer]:
|
||||
- generic [ref=e148]: Fri, Jan 16
|
||||
- generic [ref=e150]: No availability
|
||||
- generic [ref=e151] [cursor=pointer]:
|
||||
- generic [ref=e153]: Sat, Jan 17
|
||||
- generic [ref=e155]: No availability
|
||||
- generic [ref=e156] [cursor=pointer]:
|
||||
- generic [ref=e158]: Sun, Jan 18
|
||||
- generic [ref=e160]: No availability
|
||||
- generic [ref=e161] [cursor=pointer]:
|
||||
- generic [ref=e163]: Mon, Jan 19
|
||||
- generic [ref=e165]: No availability
|
||||
- generic [ref=e166] [cursor=pointer]:
|
||||
- generic [ref=e168]: Tue, Jan 20
|
||||
- generic [ref=e170]: No availability
|
||||
- status [ref=e171]:
|
||||
- generic [ref=e172]:
|
||||
- img [ref=e174]
|
||||
- generic [ref=e176]:
|
||||
- generic [ref=e10]:
|
||||
- text: •
|
||||
- link "Appointments" [ref=e11] [cursor=pointer]:
|
||||
- /url: /admin/appointments
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]: admin@example.com
|
||||
- button "Sign out" [ref=e14] [cursor=pointer]
|
||||
- generic [ref=e16]:
|
||||
- generic [ref=e18]:
|
||||
- heading "Availability" [level=1] [ref=e19]
|
||||
- paragraph [ref=e20]: Configure your available time slots for the next 30 days
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22] [cursor=pointer]:
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24]: Mon, Dec 22
|
||||
- button "⎘" [ref=e25]
|
||||
- generic [ref=e27]: 09:00 - 12:00
|
||||
- generic [ref=e28] [cursor=pointer]:
|
||||
- generic [ref=e30]: Tue, Dec 23
|
||||
- generic [ref=e32]: No availability
|
||||
- generic [ref=e33] [cursor=pointer]:
|
||||
- generic [ref=e35]: Wed, Dec 24
|
||||
- generic [ref=e37]: No availability
|
||||
- generic [ref=e38] [cursor=pointer]:
|
||||
- generic [ref=e40]: Thu, Dec 25
|
||||
- generic [ref=e42]: No availability
|
||||
- generic [ref=e43] [cursor=pointer]:
|
||||
- generic [ref=e45]: Fri, Dec 26
|
||||
- generic [ref=e47]: No availability
|
||||
- generic [ref=e48] [cursor=pointer]:
|
||||
- generic [ref=e50]: Sat, Dec 27
|
||||
- generic [ref=e52]: No availability
|
||||
- generic [ref=e53] [cursor=pointer]:
|
||||
- generic [ref=e55]: Sun, Dec 28
|
||||
- generic [ref=e57]: No availability
|
||||
- generic [ref=e58] [cursor=pointer]:
|
||||
- generic [ref=e60]: Mon, Dec 29
|
||||
- generic [ref=e62]: No availability
|
||||
- generic [ref=e63] [cursor=pointer]:
|
||||
- generic [ref=e65]: Tue, Dec 30
|
||||
- generic [ref=e67]: No availability
|
||||
- generic [ref=e68] [cursor=pointer]:
|
||||
- generic [ref=e70]: Wed, Dec 31
|
||||
- generic [ref=e72]: No availability
|
||||
- generic [ref=e73] [cursor=pointer]:
|
||||
- generic [ref=e75]: Thu, Jan 1
|
||||
- generic [ref=e77]: No availability
|
||||
- generic [ref=e78] [cursor=pointer]:
|
||||
- generic [ref=e80]: Fri, Jan 2
|
||||
- generic [ref=e82]: No availability
|
||||
- generic [ref=e83] [cursor=pointer]:
|
||||
- generic [ref=e85]: Sat, Jan 3
|
||||
- generic [ref=e87]: No availability
|
||||
- generic [ref=e88] [cursor=pointer]:
|
||||
- generic [ref=e90]: Sun, Jan 4
|
||||
- generic [ref=e92]: No availability
|
||||
- generic [ref=e93] [cursor=pointer]:
|
||||
- generic [ref=e95]: Mon, Jan 5
|
||||
- generic [ref=e97]: No availability
|
||||
- generic [ref=e98] [cursor=pointer]:
|
||||
- generic [ref=e100]: Tue, Jan 6
|
||||
- generic [ref=e102]: No availability
|
||||
- generic [ref=e103] [cursor=pointer]:
|
||||
- generic [ref=e105]: Wed, Jan 7
|
||||
- generic [ref=e107]: No availability
|
||||
- generic [ref=e108] [cursor=pointer]:
|
||||
- generic [ref=e110]: Thu, Jan 8
|
||||
- generic [ref=e112]: No availability
|
||||
- generic [ref=e113] [cursor=pointer]:
|
||||
- generic [ref=e115]: Fri, Jan 9
|
||||
- generic [ref=e117]: No availability
|
||||
- generic [ref=e118] [cursor=pointer]:
|
||||
- generic [ref=e120]: Sat, Jan 10
|
||||
- generic [ref=e122]: No availability
|
||||
- generic [ref=e123] [cursor=pointer]:
|
||||
- generic [ref=e125]: Sun, Jan 11
|
||||
- generic [ref=e127]: No availability
|
||||
- generic [ref=e128] [cursor=pointer]:
|
||||
- generic [ref=e130]: Mon, Jan 12
|
||||
- generic [ref=e132]: No availability
|
||||
- generic [ref=e133] [cursor=pointer]:
|
||||
- generic [ref=e135]: Tue, Jan 13
|
||||
- generic [ref=e137]: No availability
|
||||
- generic [ref=e138] [cursor=pointer]:
|
||||
- generic [ref=e140]: Wed, Jan 14
|
||||
- generic [ref=e142]: No availability
|
||||
- generic [ref=e143] [cursor=pointer]:
|
||||
- generic [ref=e145]: Thu, Jan 15
|
||||
- generic [ref=e147]: No availability
|
||||
- generic [ref=e148] [cursor=pointer]:
|
||||
- generic [ref=e150]: Fri, Jan 16
|
||||
- generic [ref=e152]: No availability
|
||||
- generic [ref=e153] [cursor=pointer]:
|
||||
- generic [ref=e155]: Sat, Jan 17
|
||||
- generic [ref=e157]: No availability
|
||||
- generic [ref=e158] [cursor=pointer]:
|
||||
- generic [ref=e160]: Sun, Jan 18
|
||||
- generic [ref=e162]: No availability
|
||||
- generic [ref=e163] [cursor=pointer]:
|
||||
- generic [ref=e165]: Mon, Jan 19
|
||||
- generic [ref=e167]: No availability
|
||||
- generic [ref=e168] [cursor=pointer]:
|
||||
- generic [ref=e170]: Tue, Jan 20
|
||||
- generic [ref=e172]: No availability
|
||||
- status [ref=e173]:
|
||||
- generic [ref=e174]:
|
||||
- img [ref=e176]
|
||||
- generic [ref=e178]:
|
||||
- text: Static route
|
||||
- button "Hide static indicator" [ref=e177] [cursor=pointer]:
|
||||
- img [ref=e178]
|
||||
- alert [ref=e181]
|
||||
- button "Hide static indicator" [ref=e179] [cursor=pointer]:
|
||||
- img [ref=e180]
|
||||
- alert [ref=e183]
|
||||
```
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- main [ref=e2]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- link "Audit" [ref=e6] [cursor=pointer]:
|
||||
- /url: /audit
|
||||
- generic [ref=e7]:
|
||||
- text: •
|
||||
- link "Invites" [ref=e8] [cursor=pointer]:
|
||||
- /url: /admin/invites
|
||||
- generic [ref=e9]: •Availability
|
||||
- generic [ref=e10]:
|
||||
- generic [ref=e11]: admin@example.com
|
||||
- button "Sign out" [ref=e12] [cursor=pointer]
|
||||
- generic [ref=e14]:
|
||||
- generic [ref=e16]:
|
||||
- heading "Availability" [level=1] [ref=e17]
|
||||
- paragraph [ref=e18]: Configure your available time slots for the next 30 days
|
||||
- generic [ref=e19]:
|
||||
- generic [ref=e20] [cursor=pointer]:
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22]: Mon, Dec 22
|
||||
- button "⎘" [ref=e23]
|
||||
- generic [ref=e25]: 09:00 - 12:00
|
||||
- generic [ref=e26] [cursor=pointer]:
|
||||
- generic [ref=e28]: Tue, Dec 23
|
||||
- generic [ref=e30]: No availability
|
||||
- generic [ref=e31] [cursor=pointer]:
|
||||
- generic [ref=e33]: Wed, Dec 24
|
||||
- generic [ref=e35]: No availability
|
||||
- generic [ref=e36] [cursor=pointer]:
|
||||
- generic [ref=e38]: Thu, Dec 25
|
||||
- generic [ref=e40]: No availability
|
||||
- generic [ref=e41] [cursor=pointer]:
|
||||
- generic [ref=e43]: Fri, Dec 26
|
||||
- generic [ref=e45]: No availability
|
||||
- generic [ref=e46] [cursor=pointer]:
|
||||
- generic [ref=e48]: Sat, Dec 27
|
||||
- generic [ref=e50]: No availability
|
||||
- generic [ref=e51] [cursor=pointer]:
|
||||
- generic [ref=e53]: Sun, Dec 28
|
||||
- generic [ref=e55]: No availability
|
||||
- generic [ref=e56] [cursor=pointer]:
|
||||
- generic [ref=e58]: Mon, Dec 29
|
||||
- generic [ref=e60]: No availability
|
||||
- generic [ref=e61] [cursor=pointer]:
|
||||
- generic [ref=e63]: Tue, Dec 30
|
||||
- generic [ref=e65]: No availability
|
||||
- generic [ref=e66] [cursor=pointer]:
|
||||
- generic [ref=e68]: Wed, Dec 31
|
||||
- generic [ref=e70]: No availability
|
||||
- generic [ref=e71] [cursor=pointer]:
|
||||
- generic [ref=e73]: Thu, Jan 1
|
||||
- generic [ref=e75]: No availability
|
||||
- generic [ref=e76] [cursor=pointer]:
|
||||
- generic [ref=e78]: Fri, Jan 2
|
||||
- generic [ref=e80]: No availability
|
||||
- generic [ref=e81] [cursor=pointer]:
|
||||
- generic [ref=e83]: Sat, Jan 3
|
||||
- generic [ref=e85]: No availability
|
||||
- generic [ref=e86] [cursor=pointer]:
|
||||
- generic [ref=e88]: Sun, Jan 4
|
||||
- generic [ref=e90]: No availability
|
||||
- generic [ref=e91] [cursor=pointer]:
|
||||
- generic [ref=e93]: Mon, Jan 5
|
||||
- generic [ref=e95]: No availability
|
||||
- generic [ref=e96] [cursor=pointer]:
|
||||
- generic [ref=e98]: Tue, Jan 6
|
||||
- generic [ref=e100]: No availability
|
||||
- generic [ref=e101] [cursor=pointer]:
|
||||
- generic [ref=e103]: Wed, Jan 7
|
||||
- generic [ref=e105]: No availability
|
||||
- generic [ref=e106] [cursor=pointer]:
|
||||
- generic [ref=e108]: Thu, Jan 8
|
||||
- generic [ref=e110]: No availability
|
||||
- generic [ref=e111] [cursor=pointer]:
|
||||
- generic [ref=e113]: Fri, Jan 9
|
||||
- generic [ref=e115]: No availability
|
||||
- generic [ref=e116] [cursor=pointer]:
|
||||
- generic [ref=e118]: Sat, Jan 10
|
||||
- generic [ref=e120]: No availability
|
||||
- generic [ref=e121] [cursor=pointer]:
|
||||
- generic [ref=e123]: Sun, Jan 11
|
||||
- generic [ref=e125]: No availability
|
||||
- generic [ref=e126] [cursor=pointer]:
|
||||
- generic [ref=e128]: Mon, Jan 12
|
||||
- generic [ref=e130]: No availability
|
||||
- generic [ref=e131] [cursor=pointer]:
|
||||
- generic [ref=e133]: Tue, Jan 13
|
||||
- generic [ref=e135]: No availability
|
||||
- generic [ref=e136] [cursor=pointer]:
|
||||
- generic [ref=e138]: Wed, Jan 14
|
||||
- generic [ref=e140]: No availability
|
||||
- generic [ref=e141] [cursor=pointer]:
|
||||
- generic [ref=e143]: Thu, Jan 15
|
||||
- generic [ref=e145]: No availability
|
||||
- generic [ref=e146] [cursor=pointer]:
|
||||
- generic [ref=e148]: Fri, Jan 16
|
||||
- generic [ref=e150]: No availability
|
||||
- generic [ref=e151] [cursor=pointer]:
|
||||
- generic [ref=e153]: Sat, Jan 17
|
||||
- generic [ref=e155]: No availability
|
||||
- generic [ref=e156] [cursor=pointer]:
|
||||
- generic [ref=e158]: Sun, Jan 18
|
||||
- generic [ref=e160]: No availability
|
||||
- generic [ref=e161] [cursor=pointer]:
|
||||
- generic [ref=e163]: Mon, Jan 19
|
||||
- generic [ref=e165]: No availability
|
||||
- generic [ref=e166] [cursor=pointer]:
|
||||
- generic [ref=e168]: Tue, Jan 20
|
||||
- generic [ref=e170]: No availability
|
||||
- status [ref=e171]:
|
||||
- generic [ref=e172]:
|
||||
- img [ref=e174]
|
||||
- generic [ref=e176]:
|
||||
- text: Static route
|
||||
- button "Hide static indicator" [ref=e177] [cursor=pointer]:
|
||||
- img [ref=e178]
|
||||
- alert [ref=e181]
|
||||
```
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e2]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- link "Counter" [ref=e6] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e7]:
|
||||
- text: •
|
||||
- link "Sum" [ref=e8] [cursor=pointer]:
|
||||
- /url: /sum
|
||||
- generic [ref=e9]: •Book
|
||||
- generic [ref=e10]:
|
||||
- text: •
|
||||
- link "Appointments" [ref=e11] [cursor=pointer]:
|
||||
- /url: /appointments
|
||||
- generic [ref=e12]:
|
||||
- text: •
|
||||
- link "My Invites" [ref=e13] [cursor=pointer]:
|
||||
- /url: /invites
|
||||
- generic [ref=e14]:
|
||||
- text: •
|
||||
- link "My Profile" [ref=e15] [cursor=pointer]:
|
||||
- /url: /profile
|
||||
- generic [ref=e16]:
|
||||
- generic [ref=e17]: user@example.com
|
||||
- button "Sign out" [ref=e18] [cursor=pointer]
|
||||
- main [ref=e19]:
|
||||
- heading "Book an Appointment" [level=1] [ref=e20]
|
||||
- paragraph [ref=e21]: Select a date to see available 15-minute slots
|
||||
- generic [ref=e22]: "Request failed: 409"
|
||||
- generic [ref=e23]:
|
||||
- heading "Select a Date" [level=2] [ref=e24]
|
||||
- generic [ref=e25]:
|
||||
- button "Mon Dec 22" [ref=e26] [cursor=pointer]:
|
||||
- generic [ref=e27]: Mon
|
||||
- generic [ref=e28]: Dec 22
|
||||
- button "Tue Dec 23" [ref=e29] [cursor=pointer]:
|
||||
- generic [ref=e30]: Tue
|
||||
- generic [ref=e31]: Dec 23
|
||||
- button "Wed Dec 24" [ref=e32] [cursor=pointer]:
|
||||
- generic [ref=e33]: Wed
|
||||
- generic [ref=e34]: Dec 24
|
||||
- button "Thu Dec 25" [ref=e35] [cursor=pointer]:
|
||||
- generic [ref=e36]: Thu
|
||||
- generic [ref=e37]: Dec 25
|
||||
- button "Fri Dec 26" [ref=e38] [cursor=pointer]:
|
||||
- generic [ref=e39]: Fri
|
||||
- generic [ref=e40]: Dec 26
|
||||
- button "Sat Dec 27" [ref=e41] [cursor=pointer]:
|
||||
- generic [ref=e42]: Sat
|
||||
- generic [ref=e43]: Dec 27
|
||||
- button "Sun Dec 28" [ref=e44] [cursor=pointer]:
|
||||
- generic [ref=e45]: Sun
|
||||
- generic [ref=e46]: Dec 28
|
||||
- button "Mon Dec 29" [ref=e47] [cursor=pointer]:
|
||||
- generic [ref=e48]: Mon
|
||||
- generic [ref=e49]: Dec 29
|
||||
- button "Tue Dec 30" [ref=e50] [cursor=pointer]:
|
||||
- generic [ref=e51]: Tue
|
||||
- generic [ref=e52]: Dec 30
|
||||
- button "Wed Dec 31" [ref=e53] [cursor=pointer]:
|
||||
- generic [ref=e54]: Wed
|
||||
- generic [ref=e55]: Dec 31
|
||||
- button "Thu Jan 1" [ref=e56] [cursor=pointer]:
|
||||
- generic [ref=e57]: Thu
|
||||
- generic [ref=e58]: Jan 1
|
||||
- button "Fri Jan 2" [ref=e59] [cursor=pointer]:
|
||||
- generic [ref=e60]: Fri
|
||||
- generic [ref=e61]: Jan 2
|
||||
- button "Sat Jan 3" [ref=e62] [cursor=pointer]:
|
||||
- generic [ref=e63]: Sat
|
||||
- generic [ref=e64]: Jan 3
|
||||
- button "Sun Jan 4" [ref=e65] [cursor=pointer]:
|
||||
- generic [ref=e66]: Sun
|
||||
- generic [ref=e67]: Jan 4
|
||||
- button "Mon Jan 5" [ref=e68] [cursor=pointer]:
|
||||
- generic [ref=e69]: Mon
|
||||
- generic [ref=e70]: Jan 5
|
||||
- button "Tue Jan 6" [ref=e71] [cursor=pointer]:
|
||||
- generic [ref=e72]: Tue
|
||||
- generic [ref=e73]: Jan 6
|
||||
- button "Wed Jan 7" [ref=e74] [cursor=pointer]:
|
||||
- generic [ref=e75]: Wed
|
||||
- generic [ref=e76]: Jan 7
|
||||
- button "Thu Jan 8" [ref=e77] [cursor=pointer]:
|
||||
- generic [ref=e78]: Thu
|
||||
- generic [ref=e79]: Jan 8
|
||||
- button "Fri Jan 9" [ref=e80] [cursor=pointer]:
|
||||
- generic [ref=e81]: Fri
|
||||
- generic [ref=e82]: Jan 9
|
||||
- button "Sat Jan 10" [ref=e83] [cursor=pointer]:
|
||||
- generic [ref=e84]: Sat
|
||||
- generic [ref=e85]: Jan 10
|
||||
- button "Sun Jan 11" [ref=e86] [cursor=pointer]:
|
||||
- generic [ref=e87]: Sun
|
||||
- generic [ref=e88]: Jan 11
|
||||
- button "Mon Jan 12" [ref=e89] [cursor=pointer]:
|
||||
- generic [ref=e90]: Mon
|
||||
- generic [ref=e91]: Jan 12
|
||||
- button "Tue Jan 13" [ref=e92] [cursor=pointer]:
|
||||
- generic [ref=e93]: Tue
|
||||
- generic [ref=e94]: Jan 13
|
||||
- button "Wed Jan 14" [ref=e95] [cursor=pointer]:
|
||||
- generic [ref=e96]: Wed
|
||||
- generic [ref=e97]: Jan 14
|
||||
- button "Thu Jan 15" [ref=e98] [cursor=pointer]:
|
||||
- generic [ref=e99]: Thu
|
||||
- generic [ref=e100]: Jan 15
|
||||
- button "Fri Jan 16" [ref=e101] [cursor=pointer]:
|
||||
- generic [ref=e102]: Fri
|
||||
- generic [ref=e103]: Jan 16
|
||||
- button "Sat Jan 17" [ref=e104] [cursor=pointer]:
|
||||
- generic [ref=e105]: Sat
|
||||
- generic [ref=e106]: Jan 17
|
||||
- button "Sun Jan 18" [ref=e107] [cursor=pointer]:
|
||||
- generic [ref=e108]: Sun
|
||||
- generic [ref=e109]: Jan 18
|
||||
- button "Mon Jan 19" [ref=e110] [cursor=pointer]:
|
||||
- generic [ref=e111]: Mon
|
||||
- generic [ref=e112]: Jan 19
|
||||
- button "Tue Jan 20" [ref=e113] [cursor=pointer]:
|
||||
- generic [ref=e114]: Tue
|
||||
- generic [ref=e115]: Jan 20
|
||||
- generic [ref=e116]:
|
||||
- heading "Available Slots for Monday, December 22" [level=2] [ref=e117]
|
||||
- generic [ref=e118]:
|
||||
- button "10:00" [ref=e119] [cursor=pointer]
|
||||
- button "10:45" [ref=e120] [cursor=pointer]
|
||||
- button "11:00" [ref=e121] [cursor=pointer]
|
||||
- button "11:15" [ref=e122] [cursor=pointer]
|
||||
- button "11:30" [ref=e123] [cursor=pointer]
|
||||
- button "11:45" [ref=e124] [cursor=pointer]
|
||||
- button "12:00" [ref=e125] [cursor=pointer]
|
||||
- button "12:15" [ref=e126] [cursor=pointer]
|
||||
- button "12:30" [ref=e127] [cursor=pointer]
|
||||
- button "12:45" [ref=e128] [cursor=pointer]
|
||||
- generic [ref=e129]:
|
||||
- heading "Confirm Booking" [level=3] [ref=e130]
|
||||
- paragraph [ref=e131]:
|
||||
- strong [ref=e132]: "Time:"
|
||||
- text: 11:00 - 11:15
|
||||
- generic [ref=e133]:
|
||||
- generic [ref=e134]: Note (optional, max 144 chars)
|
||||
- textbox "Add a note about your appointment..." [ref=e135]
|
||||
- generic [ref=e136]: 0/144
|
||||
- generic [ref=e137]:
|
||||
- button "Book Appointment" [ref=e138] [cursor=pointer]
|
||||
- button "Cancel" [ref=e139] [cursor=pointer]
|
||||
- status [ref=e140]:
|
||||
- generic [ref=e141]:
|
||||
- img [ref=e143]
|
||||
- generic [ref=e145]:
|
||||
- text: Static route
|
||||
- button "Hide static indicator" [ref=e146] [cursor=pointer]:
|
||||
- img [ref=e147]
|
||||
- alert [ref=e150]
|
||||
```
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- status [ref=e2]:
|
||||
- generic [ref=e3]:
|
||||
- img [ref=e5]
|
||||
- generic [ref=e7]:
|
||||
- text: Static route
|
||||
- button "Hide static indicator" [ref=e8] [cursor=pointer]:
|
||||
- img [ref=e9]
|
||||
- alert [ref=e12]: ...
|
||||
- main [ref=e13]:
|
||||
- generic [ref=e14]:
|
||||
- generic [ref=e15]:
|
||||
- generic [ref=e16]: Counter
|
||||
- generic [ref=e17]:
|
||||
- text: •
|
||||
- link "Sum" [ref=e18] [cursor=pointer]:
|
||||
- /url: /sum
|
||||
- generic [ref=e19]:
|
||||
- text: •
|
||||
- link "Book" [ref=e20] [cursor=pointer]:
|
||||
- /url: /booking
|
||||
- generic [ref=e21]:
|
||||
- text: •
|
||||
- link "Appointments" [ref=e22] [cursor=pointer]:
|
||||
- /url: /appointments
|
||||
- generic [ref=e23]:
|
||||
- text: •
|
||||
- link "My Invites" [ref=e24] [cursor=pointer]:
|
||||
- /url: /invites
|
||||
- generic [ref=e25]:
|
||||
- text: •
|
||||
- link "My Profile" [ref=e26] [cursor=pointer]:
|
||||
- /url: /profile
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]: counter-1766272960115-d79hw5@example.com
|
||||
- button "Sign out" [ref=e29] [cursor=pointer]
|
||||
- generic [ref=e31]:
|
||||
- generic [ref=e32]: Current Count
|
||||
- heading "0" [level=1] [ref=e33]
|
||||
- button "+ Increment" [ref=e34] [cursor=pointer]:
|
||||
- generic [ref=e35]: +
|
||||
- text: Increment
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue