feat: Dashboard redesign, plan permissions, and help docs improvements

Major updates including:
- Customizable dashboard with drag-and-drop widget grid layout
- Plan-based feature locking for plugins and tasks
- Comprehensive help documentation updates across all pages
- Plugin seeding in deployment process for all tenants
- Permission synchronization system for subscription plans
- QuotaOverageModal component and enhanced UX flows

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-03 13:02:44 -05:00
parent 9444e26924
commit dcb14503a2
66 changed files with 7099 additions and 1467 deletions

View File

@@ -102,6 +102,7 @@ const OwnerScheduler: React.FC<OwnerSchedulerProps> = ({ user, business }) => {
const [editDateTime, setEditDateTime] = useState('');
const [editResource, setEditResource] = useState('');
const [editDuration, setEditDuration] = useState(0);
const [editStatus, setEditStatus] = useState<AppointmentStatus>('CONFIRMED');
// Filter state
const [showFilterMenu, setShowFilterMenu] = useState(false);
@@ -113,9 +114,17 @@ const OwnerScheduler: React.FC<OwnerSchedulerProps> = ({ user, business }) => {
// Update edit state when selected appointment changes
useEffect(() => {
if (selectedAppointment) {
setEditDateTime(new Date(selectedAppointment.startTime).toISOString().slice(0, 16));
// Format date in local time for datetime-local input (toISOString uses UTC)
const date = new Date(selectedAppointment.startTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
setEditDateTime(`${year}-${month}-${day}T${hours}:${minutes}`);
setEditResource(selectedAppointment.resourceId || '');
setEditDuration(selectedAppointment.durationMinutes);
setEditStatus(selectedAppointment.status);
}
}, [selectedAppointment]);
@@ -551,7 +560,7 @@ const OwnerScheduler: React.FC<OwnerSchedulerProps> = ({ user, business }) => {
const getStatusColor = (status: Appointment['status'], startTime: Date, endTime: Date) => {
if (status === 'COMPLETED') return 'bg-green-100 border-green-500 text-green-900 dark:bg-green-900/50 dark:border-green-500 dark:text-green-200';
if (status === 'NO_SHOW') return 'bg-gray-100 border-gray-400 text-gray-600 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400';
if (status === 'NO_SHOW') return 'bg-orange-100 border-orange-500 text-orange-900 dark:bg-orange-900/50 dark:border-orange-500 dark:text-orange-200';
if (status === 'CANCELLED') return 'bg-gray-100 border-gray-400 text-gray-500 opacity-75 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400';
const now = new Date();
if (now > endTime) return 'bg-red-100 border-red-500 text-red-900 dark:bg-red-900/50 dark:border-red-500 dark:text-red-200';
@@ -562,7 +571,7 @@ const OwnerScheduler: React.FC<OwnerSchedulerProps> = ({ user, business }) => {
// Simplified status colors for month view (no border classes)
const getMonthStatusColor = (status: Appointment['status'], startTime: Date, endTime: Date) => {
if (status === 'COMPLETED') return 'bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-200 hover:bg-green-200 dark:hover:bg-green-800/50';
if (status === 'NO_SHOW') return 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600';
if (status === 'NO_SHOW') return 'bg-orange-100 dark:bg-orange-900/50 text-orange-800 dark:text-orange-200 hover:bg-orange-200 dark:hover:bg-orange-800/50';
if (status === 'CANCELLED') return 'bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 opacity-75 hover:bg-gray-200 dark:hover:bg-gray-600';
const now = new Date();
if (now > endTime) return 'bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-200 hover:bg-red-200 dark:hover:bg-red-800/50';
@@ -823,6 +832,7 @@ const OwnerScheduler: React.FC<OwnerSchedulerProps> = ({ user, business }) => {
const updates: any = {
startTime: new Date(editDateTime),
durationMinutes: validDuration,
status: editStatus,
};
if (editResource) {
@@ -1112,9 +1122,9 @@ const OwnerScheduler: React.FC<OwnerSchedulerProps> = ({ user, business }) => {
<div className={`w-2 h-2 rounded-full ml-auto ${
status === 'COMPLETED' ? 'bg-green-500' :
status === 'CANCELLED' ? 'bg-gray-400' :
status === 'NO_SHOW' ? 'bg-gray-400' :
status === 'NO_SHOW' ? 'bg-orange-500' :
status === 'CONFIRMED' ? 'bg-blue-500' :
'bg-orange-400'
'bg-yellow-400'
}`}></div>
</div>
))}
@@ -1719,8 +1729,18 @@ const OwnerScheduler: React.FC<OwnerSchedulerProps> = ({ user, business }) => {
<p className="text-sm font-semibold text-gray-900 dark:text-white">{services.find(s => s.id === selectedAppointment.serviceId)?.name}</p>
</div>
<div className="p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-1">Status</p>
<p className="text-sm font-semibold text-gray-900 dark:text-white capitalize">{selectedAppointment.status.toLowerCase().replace('_', ' ')}</p>
<label className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-1 block">Status</label>
<select
value={editStatus}
onChange={(e) => setEditStatus(e.target.value as AppointmentStatus)}
className="w-full px-2 py-1 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded text-sm font-semibold text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-brand-500"
>
<option value="PENDING">Pending</option>
<option value="CONFIRMED">Confirmed</option>
<option value="COMPLETED">Completed</option>
<option value="CANCELLED">Cancelled</option>
<option value="NO_SHOW">No Show</option>
</select>
</div>
</div>