mcpengine/servers/acuity-scheduling/src/ui/react-app/availability-calendar.ts
Jake Shore f8e0b3246f feat: Complete Acuity Scheduling MCP server with 40+ tools and 14 React apps
- Full API client with Basic Auth and OAuth2 support
- 40+ tools across 10 categories (appointments, availability, clients, calendars, products, forms, labels, webhooks, coupons, blocks)
- 14 interactive React-based MCP apps for rich UI experiences
- Comprehensive error handling and pagination
- TypeScript implementation with full type definitions
- Complete documentation and examples
2026-02-12 17:41:55 -05:00

164 lines
6.7 KiB
TypeScript

export default `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Availability Calendar</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 900px; margin: 0 auto; }
h1 { color: #333; margin-bottom: 20px; }
.calendar-header { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
.calendar-header select { padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-right: 10px; }
.calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 10px; background: white; padding: 20px; border-radius: 8px; }
.day-header { text-align: center; font-weight: 600; color: #666; padding: 10px; font-size: 14px; }
.day-cell { aspect-ratio: 1; border: 2px solid #E5E7EB; border-radius: 8px; padding: 8px; cursor: pointer; transition: all 0.2s; position: relative; }
.day-cell:hover { border-color: #4F46E5; }
.day-cell.available { background: #D1FAE5; border-color: #10B981; }
.day-cell.limited { background: #FEF3C7; border-color: #F59E0B; }
.day-cell.unavailable { background: #F3F4F6; border-color: #D1D5DB; cursor: not-allowed; }
.day-cell.selected { border-color: #4F46E5; border-width: 3px; }
.day-number { font-weight: 600; font-size: 16px; margin-bottom: 4px; }
.slots-available { font-size: 11px; color: #666; }
.time-slots { background: white; padding: 20px; border-radius: 8px; margin-top: 20px; }
.time-slot { display: inline-block; padding: 8px 16px; margin: 5px; border: 1px solid #ddd; border-radius: 6px; cursor: pointer; background: white; }
.time-slot:hover { background: #4F46E5; color: white; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
function AvailabilityCalendar() {
const [selectedDate, setSelectedDate] = useState(null);
const [appointmentType, setAppointmentType] = useState('1');
const [calendar, setCalendar] = useState('all');
const [availabilityData, setAvailabilityData] = useState({});
const [timeSlots, setTimeSlots] = useState([]);
useEffect(() => {
fetchAvailability();
}, [appointmentType, calendar]);
useEffect(() => {
if (selectedDate) {
fetchTimeSlots(selectedDate);
}
}, [selectedDate]);
const fetchAvailability = async () => {
// Mock availability data
const mockData = {
'2024-01-15': { available: true, slots: 8 },
'2024-01-16': { available: true, slots: 5 },
'2024-01-17': { available: true, slots: 3 },
'2024-01-18': { available: true, slots: 10 },
'2024-01-19': { available: true, slots: 2 },
'2024-01-22': { available: true, slots: 6 },
'2024-01-23': { available: false, slots: 0 },
};
setAvailabilityData(mockData);
};
const fetchTimeSlots = async (date) => {
// Mock time slots
const mockSlots = [
'09:00 AM', '10:00 AM', '11:00 AM', '01:00 PM', '02:00 PM', '03:00 PM', '04:00 PM'
];
setTimeSlots(mockSlots);
};
const getDaysInMonth = () => {
const days = [];
const firstDay = new Date(2024, 0, 1);
const lastDay = new Date(2024, 0, 31);
for (let d = new Date(firstDay); d <= lastDay; d.setDate(d.getDate() + 1)) {
days.push(new Date(d));
}
return days;
};
const getDayClass = (date) => {
const dateStr = date.toISOString().split('T')[0];
const availability = availabilityData[dateStr];
if (!availability) return 'unavailable';
if (availability.slots > 5) return 'available';
if (availability.slots > 0) return 'limited';
return 'unavailable';
};
const handleDateClick = (date) => {
const dateStr = date.toISOString().split('T')[0];
const availability = availabilityData[dateStr];
if (availability && availability.slots > 0) {
setSelectedDate(dateStr);
}
};
return (
<div className="container">
<h1>Availability Calendar</h1>
<div className="calendar-header">
<select value={appointmentType} onChange={(e) => setAppointmentType(e.target.value)}>
<option value="1">Initial Consultation</option>
<option value="2">Follow-up Appointment</option>
<option value="3">Assessment</option>
</select>
<select value={calendar} onChange={(e) => setCalendar(e.target.value)}>
<option value="all">All Calendars</option>
<option value="dr-smith">Dr. Smith</option>
<option value="dr-johnson">Dr. Johnson</option>
</select>
</div>
<div className="calendar-grid">
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => (
<div key={day} className="day-header">{day}</div>
))}
{getDaysInMonth().map((date, idx) => {
const dateStr = date.toISOString().split('T')[0];
const availability = availabilityData[dateStr];
const isSelected = selectedDate === dateStr;
return (
<div
key={idx}
className={\`day-cell \${getDayClass(date)} \${isSelected ? 'selected' : ''}\`}
onClick={() => handleDateClick(date)}
>
<div className="day-number">{date.getDate()}</div>
{availability && availability.slots > 0 && (
<div className="slots-available">{availability.slots} slots</div>
)}
</div>
);
})}
</div>
{selectedDate && timeSlots.length > 0 && (
<div className="time-slots">
<h2>Available Times for {selectedDate}</h2>
<div>
{timeSlots.map(slot => (
<button key={slot} className="time-slot">{slot}</button>
))}
</div>
</div>
)}
</div>
);
}
ReactDOM.render(<AvailabilityCalendar />, document.getElementById('root'));
</script>
</body>
</html>`;