theming, date, time localisation, additional features, seeding initial cleanup
This commit is contained in:
@@ -0,0 +1,279 @@
|
|||||||
|
# Database Setup Guide
|
||||||
|
|
||||||
|
This guide explains how to set up and manage the database for the Table Tennis Booking System.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Full Setup (Recommended for new installations)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:setup
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
- ✅ Create all database tables
|
||||||
|
- ✅ Seed essential data (users, courts, settings, time slots)
|
||||||
|
- ✅ Add sample data (bookings, announcements, activity logs)
|
||||||
|
- ✅ Display a comprehensive summary
|
||||||
|
|
||||||
|
### Essential Data Only
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:seed
|
||||||
|
```
|
||||||
|
|
||||||
|
This will set up the database with only essential data, no sample bookings or logs.
|
||||||
|
|
||||||
|
## Database Scripts Overview
|
||||||
|
|
||||||
|
### 🚀 Setup Scripts
|
||||||
|
|
||||||
|
| Script | Command | Description |
|
||||||
|
| ------------------- | ----------------------------------------- | ---------------------------------------- |
|
||||||
|
| **Full Setup** | `npm run db:setup` | Complete database setup with sample data |
|
||||||
|
| **Essential Setup** | `npm run db:seed` | Database setup with essential data only |
|
||||||
|
| **Advanced Setup** | `tsx scripts/setup-database.ts [options]` | Setup with custom options |
|
||||||
|
|
||||||
|
#### Setup Options:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tsx scripts/setup-database.ts --help # Show all options
|
||||||
|
tsx scripts/setup-database.ts --reset # Reset database first
|
||||||
|
tsx scripts/setup-database.ts --essential-only # No sample data
|
||||||
|
tsx scripts/setup-database.ts --verbose # Show detailed output
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🗑️ Reset Scripts
|
||||||
|
|
||||||
|
| Script | Command | Description |
|
||||||
|
| ------------------- | ----------------------------------- | ------------------------------------ |
|
||||||
|
| **Safe Reset** | `npm run db:reset` | Shows warning, requires confirmation |
|
||||||
|
| **Confirmed Reset** | `npm run db:reset-confirm` | Immediate reset (destructive!) |
|
||||||
|
| **Advanced Reset** | `tsx scripts/reset-db.ts [options]` | Reset with custom options |
|
||||||
|
|
||||||
|
#### Reset Options:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tsx scripts/reset-db.ts --help # Show all options
|
||||||
|
tsx scripts/reset-db.ts --confirm # Confirm destructive operation
|
||||||
|
tsx scripts/reset-db.ts --verbose # Show detailed output
|
||||||
|
tsx scripts/reset-db.ts --keep-data # Preserve data where possible
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🛠️ Drizzle Scripts
|
||||||
|
|
||||||
|
| Script | Command | Description |
|
||||||
|
| ------------------ | -------------------- | ---------------------------------- |
|
||||||
|
| **Push Schema** | `npm run db:push` | Push schema changes to database |
|
||||||
|
| **Run Migrations** | `npm run db:migrate` | Apply migration files |
|
||||||
|
| **Studio** | `npm run db:studio` | Open Drizzle Studio (database GUI) |
|
||||||
|
|
||||||
|
## Database Structure
|
||||||
|
|
||||||
|
### Tables Created
|
||||||
|
|
||||||
|
1. **users** - User accounts and authentication
|
||||||
|
2. **courts** - Table tennis courts configuration
|
||||||
|
3. **bookings** - Court reservations and scheduling
|
||||||
|
4. **announcements** - System announcements and notifications
|
||||||
|
5. **time_slots** - Available booking time slots per day
|
||||||
|
6. **settings** - System configuration and preferences
|
||||||
|
7. **activity_logs** - User action tracking and audit trail
|
||||||
|
8. **metrics** - System performance and usage metrics
|
||||||
|
|
||||||
|
### Default Data
|
||||||
|
|
||||||
|
#### Users
|
||||||
|
|
||||||
|
- **Admin User**: `admin@tabletennis.com` / `admin123`
|
||||||
|
- **Test User**: `user@tabletennis.com` / `user123`
|
||||||
|
|
||||||
|
#### Courts
|
||||||
|
|
||||||
|
- Court 1 (Active)
|
||||||
|
- Court 2 (Active)
|
||||||
|
- Court 3 (Active)
|
||||||
|
|
||||||
|
#### Time Slots
|
||||||
|
|
||||||
|
- **Sunday**: 12:00 - 17:00
|
||||||
|
- **Monday-Thursday**: 18:00 - 23:00
|
||||||
|
- **Friday**: 17:00 - 22:00
|
||||||
|
- **Saturday**: 10:00 - 18:00
|
||||||
|
|
||||||
|
#### Settings
|
||||||
|
|
||||||
|
- Booking window: 14 days
|
||||||
|
- Max booking duration: 2 hours
|
||||||
|
- Min booking duration: 60 minutes
|
||||||
|
- Booking modifications allowed: Yes
|
||||||
|
- Modification cutoff: 2 hours before booking
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### 🆕 New Project Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone <repository-url>
|
||||||
|
cd tt-booking
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Set up database with full sample data
|
||||||
|
npm run db:setup
|
||||||
|
|
||||||
|
# Start development server
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔄 Development Reset
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reset and rebuild database
|
||||||
|
npm run db:reset-confirm
|
||||||
|
npm run db:setup
|
||||||
|
|
||||||
|
# Or combine in one command
|
||||||
|
tsx scripts/setup-database.ts --reset --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🚀 Production Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Essential data only (no sample bookings)
|
||||||
|
npm run db:seed
|
||||||
|
|
||||||
|
# Or with custom options
|
||||||
|
tsx scripts/setup-database.ts --essential-only --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🧪 Testing Environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reset database and add fresh test data
|
||||||
|
tsx scripts/reset-db.ts --confirm --verbose
|
||||||
|
tsx scripts/setup-database.ts --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
|
||||||
|
### Custom Time Slots
|
||||||
|
|
||||||
|
Edit the time slots in `scripts/setup-database.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const timeSlotData = [
|
||||||
|
{ dayOfWeek: 0, startTime: '10:00', endTime: '16:00' }, // Sunday
|
||||||
|
{ dayOfWeek: 1, startTime: '17:00', endTime: '22:00' }, // Monday
|
||||||
|
// ... customize as needed
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Settings
|
||||||
|
|
||||||
|
Modify default settings in the `seedBasicData` function:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const defaultSettings = [
|
||||||
|
{ key: 'booking_window_days', value: '7' }, // 7 days ahead
|
||||||
|
{ key: 'max_booking_duration_hours', value: '3' }, // 3 hour max
|
||||||
|
// ... add more settings
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Additional Users
|
||||||
|
|
||||||
|
Add more users in the `seedBasicData` or `seedSampleData` functions:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await db.insert(schema.users).values({
|
||||||
|
id: randomUUID(),
|
||||||
|
email: 'coach@tabletennis.com',
|
||||||
|
name: 'Head',
|
||||||
|
surname: 'Coach',
|
||||||
|
password: await bcrypt.hash('coach123', 12),
|
||||||
|
role: 'user',
|
||||||
|
themePreference: 'system',
|
||||||
|
createdAt: new Date(now),
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**Database locked error**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Close any running applications using the database
|
||||||
|
# Then reset and recreate
|
||||||
|
npm run db:reset-confirm
|
||||||
|
npm run db:setup
|
||||||
|
```
|
||||||
|
|
||||||
|
**Schema mismatch**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Push latest schema changes
|
||||||
|
npm run db:push
|
||||||
|
|
||||||
|
# Or reset and rebuild
|
||||||
|
npm run db:reset-confirm
|
||||||
|
npm run db:setup
|
||||||
|
```
|
||||||
|
|
||||||
|
**Permission errors**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check file permissions
|
||||||
|
ls -la sqlite.db
|
||||||
|
|
||||||
|
# If needed, fix permissions
|
||||||
|
chmod 666 sqlite.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Location
|
||||||
|
|
||||||
|
The SQLite database file is located at: `./sqlite.db`
|
||||||
|
|
||||||
|
You can:
|
||||||
|
|
||||||
|
- View it with any SQLite browser
|
||||||
|
- Open it in Drizzle Studio: `npm run db:studio`
|
||||||
|
- Back it up by copying the file
|
||||||
|
- Remove it completely and recreate with setup scripts
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show help for setup script
|
||||||
|
tsx scripts/setup-database.ts --help
|
||||||
|
|
||||||
|
# Show help for reset script
|
||||||
|
tsx scripts/reset-db.ts --help
|
||||||
|
|
||||||
|
# Show all available npm scripts
|
||||||
|
npm run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration from Old Scripts
|
||||||
|
|
||||||
|
The new unified scripts replace the old individual seed scripts:
|
||||||
|
|
||||||
|
| Old Script | New Equivalent |
|
||||||
|
| ------------------------------- | ----------------------------------- |
|
||||||
|
| `scripts/seed-data.ts` | Integrated into `setup-database.ts` |
|
||||||
|
| `scripts/seed-announcements.ts` | Integrated into `setup-database.ts` |
|
||||||
|
| `scripts/seed-time-slots.ts` | Integrated into `setup-database.ts` |
|
||||||
|
| `scripts/init-admin-data.ts` | Integrated into `setup-database.ts` |
|
||||||
|
| `scripts/reset-database.ts` | Replaced by `reset-db.ts` |
|
||||||
|
|
||||||
|
The old scripts can be safely removed as all functionality is now consolidated in the new, more intelligent setup system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Need help?** Check the terminal output for detailed information and suggestions, or refer to the help commands above.
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
# Database Setup System - Implementation Summary
|
||||||
|
|
||||||
|
## 🎯 Objective Accomplished
|
||||||
|
|
||||||
|
Successfully consolidated all individual seed scripts into one intelligent, comprehensive database setup system.
|
||||||
|
|
||||||
|
## 📁 What Was Created
|
||||||
|
|
||||||
|
### 1. **Main Setup Script** (`scripts/setup-database.ts`)
|
||||||
|
|
||||||
|
**Unified, intelligent database setup with all functionality integrated:**
|
||||||
|
|
||||||
|
- ✅ **Schema Creation** - Creates all required tables with proper constraints
|
||||||
|
- ✅ **Essential Data** - Users, courts, settings, time slots, announcements
|
||||||
|
- ✅ **Sample Data** - Realistic bookings, activity logs, additional announcements
|
||||||
|
- ✅ **Flexible Options** - Essential-only mode, reset capability, verbose output
|
||||||
|
- ✅ **Smart Validation** - Checks existing data, handles conflicts intelligently
|
||||||
|
- ✅ **Comprehensive Summary** - Shows what was created with login credentials
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tsx scripts/setup-database.ts [options]
|
||||||
|
--reset # Drop all tables first
|
||||||
|
--essential-only # Skip sample data
|
||||||
|
--verbose # Detailed output
|
||||||
|
--help # Full documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Safe Reset Script** (`scripts/reset-db.ts`)
|
||||||
|
|
||||||
|
**Improved reset with safety features:**
|
||||||
|
|
||||||
|
- ✅ **Confirmation Required** - Prevents accidental data loss
|
||||||
|
- ✅ **Database Statistics** - Shows what will be deleted
|
||||||
|
- ✅ **Verbose Logging** - Detailed operation tracking
|
||||||
|
- ✅ **Helpful Guidance** - Clear next steps after reset
|
||||||
|
|
||||||
|
**Safety First:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tsx scripts/reset-db.ts # Shows warning, requires --confirm
|
||||||
|
tsx scripts/reset-db.ts --confirm # Actually performs reset
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **NPM Scripts Integration** (`package.json`)
|
||||||
|
|
||||||
|
**Convenient commands for all database operations:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"db:setup": "tsx scripts/setup-database.ts",
|
||||||
|
"db:reset": "tsx scripts/reset-db.ts",
|
||||||
|
"db:reset-confirm": "tsx scripts/reset-db.ts --confirm",
|
||||||
|
"db:seed": "tsx scripts/setup-database.ts --essential-only"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. **Comprehensive Documentation**
|
||||||
|
|
||||||
|
- **`DATABASE_SETUP.md`** - Complete usage guide with examples
|
||||||
|
- **`scripts/old-seeds/README.md`** - Migration guide and archive documentation
|
||||||
|
- **Inline Help** - Detailed help commands in both scripts
|
||||||
|
|
||||||
|
### 5. **Legacy Script Organization**
|
||||||
|
|
||||||
|
- Old scripts moved to `scripts/old-seeds/` (preserved for reference)
|
||||||
|
- Clean separation between old and new systems
|
||||||
|
- Migration documentation for developers
|
||||||
|
|
||||||
|
## 🚀 Key Improvements Over Old System
|
||||||
|
|
||||||
|
### **Intelligence & Automation**
|
||||||
|
|
||||||
|
| Old System | New System |
|
||||||
|
| ---------------------- | ----------------------------------------- |
|
||||||
|
| 5 separate scripts | 1 unified script |
|
||||||
|
| Manual execution order | Automatic dependency handling |
|
||||||
|
| No safety checks | Confirmation required for destructive ops |
|
||||||
|
| Basic error handling | Comprehensive validation & recovery |
|
||||||
|
|
||||||
|
### **User Experience**
|
||||||
|
|
||||||
|
| Feature | Old | New |
|
||||||
|
| ----------------- | ------------------------ | -------------------------------------- |
|
||||||
|
| **Setup Process** | Run 5+ commands manually | Single `npm run db:setup` |
|
||||||
|
| **Safety** | Easy to lose data | Confirmation required |
|
||||||
|
| **Documentation** | Scattered across files | Centralized guides |
|
||||||
|
| **Flexibility** | All or nothing | Essential-only, verbose, reset options |
|
||||||
|
|
||||||
|
### **Developer Experience**
|
||||||
|
|
||||||
|
| Aspect | Before | After |
|
||||||
|
| -------------- | --------------------- | ---------------------------------------------- |
|
||||||
|
| **Onboarding** | Complex, error-prone | Single command setup |
|
||||||
|
| **Testing** | Manual script running | `npm run db:reset-confirm && npm run db:setup` |
|
||||||
|
| **Production** | Risk of sample data | `npm run db:seed` for essentials only |
|
||||||
|
| **Debugging** | Limited visibility | Verbose mode with detailed logging |
|
||||||
|
|
||||||
|
## 📊 Default Data Created
|
||||||
|
|
||||||
|
### **Users** (Ready to use)
|
||||||
|
|
||||||
|
- **Admin**: `admin@tabletennis.com` / `admin123`
|
||||||
|
- **User**: `user@tabletennis.com` / `user123`
|
||||||
|
|
||||||
|
### **Infrastructure**
|
||||||
|
|
||||||
|
- **3 Courts** (Court 1, 2, 3)
|
||||||
|
- **12 System Settings** (booking rules, facility info)
|
||||||
|
- **7 Time Slot Configurations** (realistic operating hours)
|
||||||
|
|
||||||
|
### **Sample Content** (when full setup)
|
||||||
|
|
||||||
|
- **3 Sample Bookings** (today/tomorrow with realistic details)
|
||||||
|
- **2 Activity Log Entries** (login, booking creation)
|
||||||
|
- **5 Announcements** (welcome, rules, tournament, equipment)
|
||||||
|
- **1 Monthly Metric** (initialized for current month)
|
||||||
|
|
||||||
|
## 🎯 Usage Examples
|
||||||
|
|
||||||
|
### **New Project Setup**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repo>
|
||||||
|
cd tt-booking
|
||||||
|
npm install
|
||||||
|
npm run db:setup
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Development Reset**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:reset-confirm
|
||||||
|
npm run db:setup --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Production Deployment**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:seed # Essential data only
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Testing Environment**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:reset-confirm && npm run db:setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Benefits Achieved
|
||||||
|
|
||||||
|
### **For New Developers**
|
||||||
|
|
||||||
|
- **One Command Setup** - `npm run db:setup` gets everything ready
|
||||||
|
- **Clear Documentation** - DATABASE_SETUP.md has all answers
|
||||||
|
- **Safe Exploration** - Can reset/rebuild anytime without fear
|
||||||
|
|
||||||
|
### **For Existing Developers**
|
||||||
|
|
||||||
|
- **Backwards Compatible** - Old data preserved, new commands available
|
||||||
|
- **Migration Path** - Old scripts archived with clear upgrade guide
|
||||||
|
- **Enhanced Safety** - Confirmation required for destructive operations
|
||||||
|
|
||||||
|
### **For Production**
|
||||||
|
|
||||||
|
- **Essential-Only Mode** - No test data in production
|
||||||
|
- **Configuration Flexibility** - Easy to customize default settings
|
||||||
|
- **Audit Trail** - Verbose logging for operations
|
||||||
|
|
||||||
|
### **For Maintenance**
|
||||||
|
|
||||||
|
- **Single Source of Truth** - All database setup in one place
|
||||||
|
- **Intelligent Updates** - Add new features to one script
|
||||||
|
- **Version Control Friendly** - One file to review for changes
|
||||||
|
|
||||||
|
## 🔧 Technical Architecture
|
||||||
|
|
||||||
|
### **Smart Schema Management**
|
||||||
|
|
||||||
|
- Uses `CREATE TABLE IF NOT EXISTS` for safety
|
||||||
|
- Proper foreign key constraints and data types
|
||||||
|
- Theme preference support (light/dark/system)
|
||||||
|
- Partner name support for bookings
|
||||||
|
|
||||||
|
### **Intelligent Data Seeding**
|
||||||
|
|
||||||
|
- Checks for existing data before inserting
|
||||||
|
- Generates realistic timestamps and relationships
|
||||||
|
- Creates proper UUID primary keys
|
||||||
|
- Handles optional fields (partner names, expiration dates)
|
||||||
|
|
||||||
|
### **Error Handling & Recovery**
|
||||||
|
|
||||||
|
- Graceful failure with helpful error messages
|
||||||
|
- Database connection cleanup in finally blocks
|
||||||
|
- Comprehensive validation before operations
|
||||||
|
- Detailed logging for debugging
|
||||||
|
|
||||||
|
## 🎉 Mission Accomplished
|
||||||
|
|
||||||
|
✅ **All old seed scripts consolidated** into one intelligent system
|
||||||
|
✅ **Database setup process simplified** to single command
|
||||||
|
✅ **Safety features implemented** to prevent data loss
|
||||||
|
✅ **Comprehensive documentation** created for all scenarios
|
||||||
|
✅ **Flexible options** for different use cases
|
||||||
|
✅ **NPM integration** for easy command access
|
||||||
|
✅ **Legacy preservation** with clear migration path
|
||||||
|
|
||||||
|
The new system provides a professional, maintainable, and user-friendly database setup experience that scales from development to production!
|
||||||
@@ -5,14 +5,14 @@ import { timeSlots } from '@/lib/db/schema';
|
|||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { logActivity, ACTIONS, ENTITY_TYPES } from '@/lib/activity-logger';
|
import { logActivity, ACTIONS, ENTITY_TYPES } from '@/lib/activity-logger';
|
||||||
|
|
||||||
export async function PUT(request: NextRequest, { params }: { params: { id: string } }) {
|
export async function PUT(request: NextRequest, context: { params: Promise<{ id: string }> }) {
|
||||||
try {
|
try {
|
||||||
const session = await getSession();
|
const session = await getSession();
|
||||||
if (!session || session.role !== 'admin') {
|
if (!session || session.role !== 'admin') {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = params;
|
const { id } = await context.params;
|
||||||
const { dayOfWeek, startTime, endTime, isActive } = await request.json();
|
const { dayOfWeek, startTime, endTime, isActive } = await request.json();
|
||||||
|
|
||||||
// Check if time slot exists
|
// Check if time slot exists
|
||||||
@@ -68,14 +68,14 @@ export async function PUT(request: NextRequest, { params }: { params: { id: stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function DELETE(request: NextRequest, { params }: { params: { id: string } }) {
|
export async function DELETE(request: NextRequest, context: { params: Promise<{ id: string }> }) {
|
||||||
try {
|
try {
|
||||||
const session = await getSession();
|
const session = await getSession();
|
||||||
if (!session || session.role !== 'admin') {
|
if (!session || session.role !== 'admin') {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = params;
|
const { id } = await context.params;
|
||||||
|
|
||||||
// Check if time slot exists
|
// Check if time slot exists
|
||||||
const existingTimeSlot = await db.select().from(timeSlots).where(eq(timeSlots.id, id)).limit(1);
|
const existingTimeSlot = await db.select().from(timeSlots).where(eq(timeSlots.id, id)).limit(1);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getSession } from '@/lib/session';
|
import { getSession } from '@/lib/session';
|
||||||
import { db } from '@/lib/db';
|
import { db } from '@/lib/db';
|
||||||
import { bookings } from '@/lib/db/schema';
|
import { bookings, settings } from '@/lib/db/schema';
|
||||||
import { eq, and } from 'drizzle-orm';
|
import { eq, and } from 'drizzle-orm';
|
||||||
import { logActivity, ACTIONS, ENTITY_TYPES } from '@/lib/activity-logger';
|
import { logActivity, ACTIONS, ENTITY_TYPES } from '@/lib/activity-logger';
|
||||||
|
|
||||||
@@ -28,15 +28,38 @@ export async function PATCH(request: NextRequest, { params }: { params: { id: st
|
|||||||
|
|
||||||
const booking = existingBooking[0];
|
const booking = existingBooking[0];
|
||||||
|
|
||||||
// Check if booking can be modified (more than 2 hours before start time)
|
// Check if booking modifications are allowed
|
||||||
|
const modificationSetting = await db
|
||||||
|
.select()
|
||||||
|
.from(settings)
|
||||||
|
.where(eq(settings.key, 'allow_booking_modifications'))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (modificationSetting.length > 0 && modificationSetting[0].value !== 'true') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Booking modifications are currently disabled by administrator' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the time restriction setting
|
||||||
|
const timeSetting = await db
|
||||||
|
.select()
|
||||||
|
.from(settings)
|
||||||
|
.where(eq(settings.key, 'booking_modification_hours_before'))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
const requiredHours = timeSetting.length > 0 ? parseFloat(timeSetting[0].value) : 2;
|
||||||
|
|
||||||
|
// Check if booking can be modified (more than required hours before start time)
|
||||||
const bookingDateTime = new Date(`${booking.date}T${booking.startTime}`);
|
const bookingDateTime = new Date(`${booking.date}T${booking.startTime}`);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const timeDiff = bookingDateTime.getTime() - now.getTime();
|
const timeDiff = bookingDateTime.getTime() - now.getTime();
|
||||||
const hoursDiff = timeDiff / (1000 * 60 * 60);
|
const hoursDiff = timeDiff / (1000 * 60 * 60);
|
||||||
|
|
||||||
if (hoursDiff <= 2) {
|
if (hoursDiff <= requiredHours) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Booking can only be modified more than 2 hours before the session' },
|
{ error: `Booking can only be modified more than ${requiredHours} hours before the session` },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -97,15 +120,38 @@ export async function DELETE(request: NextRequest, { params }: { params: { id: s
|
|||||||
|
|
||||||
const booking = existingBooking[0];
|
const booking = existingBooking[0];
|
||||||
|
|
||||||
// Check if booking can be cancelled (more than 2 hours before start time)
|
// Check if booking modifications are allowed
|
||||||
|
const modificationSetting = await db
|
||||||
|
.select()
|
||||||
|
.from(settings)
|
||||||
|
.where(eq(settings.key, 'allow_booking_modifications'))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (modificationSetting.length > 0 && modificationSetting[0].value !== 'true') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Booking modifications are currently disabled by administrator' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the time restriction setting
|
||||||
|
const timeSetting = await db
|
||||||
|
.select()
|
||||||
|
.from(settings)
|
||||||
|
.where(eq(settings.key, 'booking_modification_hours_before'))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
const requiredHours = timeSetting.length > 0 ? parseFloat(timeSetting[0].value) : 2;
|
||||||
|
|
||||||
|
// Check if booking can be cancelled (more than required hours before start time)
|
||||||
const bookingDateTime = new Date(`${booking.date}T${booking.startTime}`);
|
const bookingDateTime = new Date(`${booking.date}T${booking.startTime}`);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const timeDiff = bookingDateTime.getTime() - now.getTime();
|
const timeDiff = bookingDateTime.getTime() - now.getTime();
|
||||||
const hoursDiff = timeDiff / (1000 * 60 * 60);
|
const hoursDiff = timeDiff / (1000 * 60 * 60);
|
||||||
|
|
||||||
if (hoursDiff <= 2) {
|
if (hoursDiff <= requiredHours) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Booking can only be cancelled more than 2 hours before the session' },
|
{ error: `Booking can only be cancelled more than ${requiredHours} hours before the session` },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export async function GET(request: NextRequest) {
|
|||||||
name: users.name,
|
name: users.name,
|
||||||
surname: users.surname,
|
surname: users.surname,
|
||||||
role: users.role,
|
role: users.role,
|
||||||
|
themePreference: users.themePreference,
|
||||||
createdAt: users.createdAt,
|
createdAt: users.createdAt,
|
||||||
})
|
})
|
||||||
.from(users)
|
.from(users)
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getSession } from '@/lib/session';
|
||||||
|
import { db } from '@/lib/db';
|
||||||
|
import { users } from '@/lib/db/schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const session = await getSession();
|
||||||
|
if (!session) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await db
|
||||||
|
.select({
|
||||||
|
themePreference: users.themePreference,
|
||||||
|
})
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, session.userId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (user.length === 0) {
|
||||||
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
themePreference: user[0].themePreference,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching theme preference:', error);
|
||||||
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PUT(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const session = await getSession();
|
||||||
|
if (!session) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { themePreference } = await request.json();
|
||||||
|
|
||||||
|
if (!themePreference || !['light', 'dark', 'system'].includes(themePreference)) {
|
||||||
|
return NextResponse.json({ error: 'Invalid theme preference' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(users)
|
||||||
|
.set({
|
||||||
|
themePreference,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(users.id, session.userId));
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
message: 'Theme preference updated successfully',
|
||||||
|
themePreference,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating theme preference:', error);
|
||||||
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ export default async function DashboardPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100'>
|
<div className='min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800'>
|
||||||
<DashboardHeader user={userWithSession} />
|
<DashboardHeader user={userWithSession} />
|
||||||
|
|
||||||
<main className='container mx-auto px-4 py-8'>
|
<main className='container mx-auto px-4 py-8'>
|
||||||
@@ -46,12 +46,12 @@ export default async function DashboardPage() {
|
|||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className='lg:col-span-2 space-y-6'>
|
<div className='lg:col-span-2 space-y-6'>
|
||||||
<div>
|
<div>
|
||||||
<h1 className='text-3xl font-bold text-gray-900 mb-2'>
|
<h1 className='text-3xl font-bold text-foreground mb-2'>
|
||||||
Welcome back,{' '}
|
Welcome back,{' '}
|
||||||
{user.name && user.surname ? `${user.name} ${user.surname}` : user.email.split('@')[0]}!
|
{user.name && user.surname ? `${user.name} ${user.surname}` : user.email.split('@')[0]}!
|
||||||
🏓
|
🏓
|
||||||
</h1>
|
</h1>
|
||||||
<p className='text-gray-600'>Book your table tennis court and enjoy your game</p>
|
<p className='text-muted-foreground'>Book your table tennis court and enjoy your game</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<EnhancedBookingCalendar />
|
<EnhancedBookingCalendar />
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
return (
|
return (
|
||||||
<html lang='en' suppressHydrationWarning>
|
<html lang='en' suppressHydrationWarning>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<ThemeProvider attribute='class' defaultTheme='light' enableSystem disableTransitionOnChange>
|
<ThemeProvider attribute='class' defaultTheme='system' enableSystem disableTransitionOnChange>
|
||||||
{children}
|
{children}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
+5
-5
@@ -3,18 +3,18 @@ import { LoginForm } from '@/components/auth/LoginForm';
|
|||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
return (
|
return (
|
||||||
<div className='min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center px-4'>
|
<div className='min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800 flex items-center justify-center px-4'>
|
||||||
<div className='w-full max-w-md space-y-6'>
|
<div className='w-full max-w-md space-y-6'>
|
||||||
<div className='text-center'>
|
<div className='text-center'>
|
||||||
<h1 className='text-3xl font-bold text-gray-900 mb-2'>🏓 TT Booking</h1>
|
<h1 className='text-3xl font-bold text-foreground mb-2'>🏓 TT Booking</h1>
|
||||||
<p className='text-gray-600'>Professional table tennis court booking system</p>
|
<p className='text-muted-foreground'>Professional table tennis court booking system</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
|
|
||||||
<div className='text-center text-sm'>
|
<div className='text-center text-sm'>
|
||||||
<span className='text-gray-600'>Don't have an account? </span>
|
<span className='text-muted-foreground'>Don't have an account? </span>
|
||||||
<Link href='/register' className='text-blue-600 hover:text-blue-800 font-medium'>
|
<Link href='/register' className='text-primary hover:text-primary/80 font-medium'>
|
||||||
Sign up
|
Sign up
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ import { RegisterForm } from '@/components/auth/RegisterForm';
|
|||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
return (
|
return (
|
||||||
<div className='min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center px-4'>
|
<div className='min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800 flex items-center justify-center px-4'>
|
||||||
<div className='w-full max-w-md space-y-6'>
|
<div className='w-full max-w-md space-y-6'>
|
||||||
<div className='text-center'>
|
<div className='text-center'>
|
||||||
<h1 className='text-3xl font-bold text-gray-900 mb-2'>🏓 TT Booking</h1>
|
<h1 className='text-3xl font-bold text-foreground mb-2'>🏓 TT Booking</h1>
|
||||||
<p className='text-gray-600'>Join our table tennis community</p>
|
<p className='text-muted-foreground'>Join our table tennis community</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RegisterForm />
|
<RegisterForm />
|
||||||
|
|
||||||
<div className='text-center text-sm'>
|
<div className='text-center text-sm'>
|
||||||
<span className='text-gray-600'>Already have an account? </span>
|
<span className='text-muted-foreground'>Already have an account? </span>
|
||||||
<Link href='/login' className='text-blue-600 hover:text-blue-800 font-medium'>
|
<Link href='/login' className='text-primary hover:text-primary/80 font-medium'>
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -236,13 +236,13 @@ export function AdminAnnouncementManagement() {
|
|||||||
const getPriorityColor = (priority: string) => {
|
const getPriorityColor = (priority: string) => {
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case 'high':
|
case 'high':
|
||||||
return 'text-red-600 bg-red-50';
|
return 'text-destructive bg-destructive/10 dark:bg-destructive/20';
|
||||||
case 'medium':
|
case 'medium':
|
||||||
return 'text-yellow-600 bg-yellow-50';
|
return 'text-amber-600 bg-amber-50 dark:text-amber-400 dark:bg-amber-950/50';
|
||||||
case 'low':
|
case 'low':
|
||||||
return 'text-green-600 bg-green-50';
|
return 'text-green-600 bg-green-50 dark:text-green-400 dark:bg-green-950/50';
|
||||||
default:
|
default:
|
||||||
return 'text-gray-600 bg-gray-50';
|
return 'text-muted-foreground bg-muted';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -387,8 +387,8 @@ export function AdminAnnouncementManagement() {
|
|||||||
<TableRow key={announcement.id}>
|
<TableRow key={announcement.id}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div>
|
<div>
|
||||||
<div className='font-medium'>{announcement.title}</div>
|
<div className='font-medium text-foreground'>{announcement.title}</div>
|
||||||
<div className='text-sm text-gray-500 truncate max-w-xs'>
|
<div className='text-sm text-muted-foreground truncate max-w-xs'>
|
||||||
{announcement.content}
|
{announcement.content}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -420,16 +420,16 @@ export function AdminAnnouncementManagement() {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<Calendar className='h-4 w-4 text-gray-500' />
|
<Calendar className='h-4 w-4 text-muted-foreground' />
|
||||||
{announcement.expiresAt
|
{announcement.expiresAt
|
||||||
? new Date(announcement.expiresAt).toLocaleDateString()
|
? new Date(announcement.expiresAt).toLocaleDateString('en-IE')
|
||||||
: 'Never'}
|
: 'Never'}
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<Calendar className='h-4 w-4 text-gray-500' />
|
<Calendar className='h-4 w-4 text-muted-foreground' />
|
||||||
{new Date(announcement.createdAt).toLocaleDateString()}
|
{new Date(announcement.createdAt).toLocaleDateString('en-IE')}
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@@ -445,7 +445,7 @@ export function AdminAnnouncementManagement() {
|
|||||||
variant='outline'
|
variant='outline'
|
||||||
size='sm'
|
size='sm'
|
||||||
onClick={() => handleDeleteAnnouncement(announcement.id)}
|
onClick={() => handleDeleteAnnouncement(announcement.id)}
|
||||||
className='text-red-600 hover:text-red-700'
|
className='text-destructive hover:text-destructive/90'
|
||||||
>
|
>
|
||||||
<Trash2 className='h-4 w-4' />
|
<Trash2 className='h-4 w-4' />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -456,7 +456,7 @@ export function AdminAnnouncementManagement() {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
{announcements.length === 0 && (
|
{announcements.length === 0 && (
|
||||||
<div className='text-center py-8 text-gray-500'>
|
<div className='text-center py-8 text-muted-foreground'>
|
||||||
No announcements found. Create your first announcement!
|
No announcements found. Create your first announcement!
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ export function AdminCourtManagement() {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className='font-medium'>{court.name}</h3>
|
<h3 className='font-medium'>{court.name}</h3>
|
||||||
<p className='text-sm text-gray-500'>
|
<p className='text-sm text-gray-500'>
|
||||||
Created {new Date(court.createdAt).toLocaleDateString()}
|
Created {new Date(court.createdAt).toLocaleDateString('en-IE')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ interface SettingsData {
|
|||||||
booking_end_time: string;
|
booking_end_time: string;
|
||||||
allow_weekend_bookings: string;
|
allow_weekend_bookings: string;
|
||||||
max_bookings_per_user_per_hour_per_day: string;
|
max_bookings_per_user_per_hour_per_day: string;
|
||||||
|
allow_booking_modifications: string;
|
||||||
|
booking_modification_hours_before: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AdminSettingsManagement() {
|
export function AdminSettingsManagement() {
|
||||||
@@ -35,6 +37,8 @@ export function AdminSettingsManagement() {
|
|||||||
booking_end_time: '22:00',
|
booking_end_time: '22:00',
|
||||||
allow_weekend_bookings: 'true',
|
allow_weekend_bookings: 'true',
|
||||||
max_bookings_per_user_per_hour_per_day: '1',
|
max_bookings_per_user_per_hour_per_day: '1',
|
||||||
|
allow_booking_modifications: 'true',
|
||||||
|
booking_modification_hours_before: '2',
|
||||||
});
|
});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
@@ -57,6 +61,8 @@ export function AdminSettingsManagement() {
|
|||||||
booking_end_time: '22:00',
|
booking_end_time: '22:00',
|
||||||
allow_weekend_bookings: 'true',
|
allow_weekend_bookings: 'true',
|
||||||
max_bookings_per_user_per_hour_per_day: '1',
|
max_bookings_per_user_per_hour_per_day: '1',
|
||||||
|
allow_booking_modifications: 'true',
|
||||||
|
booking_modification_hours_before: '2',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map the settings array to our object
|
// Map the settings array to our object
|
||||||
@@ -266,6 +272,43 @@ export function AdminSettingsManagement() {
|
|||||||
<p className='text-sm text-gray-500'>Maximum bookings per user per hour on the same day</p>
|
<p className='text-sm text-gray-500'>Maximum bookings per user per hour on the same day</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Booking Modification Settings */}
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<div className='flex items-center space-x-2'>
|
||||||
|
<Switch
|
||||||
|
id='allow_booking_modifications'
|
||||||
|
checked={settings.allow_booking_modifications === 'true'}
|
||||||
|
onCheckedChange={(checked: boolean) =>
|
||||||
|
updateSetting('allow_booking_modifications', checked.toString())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label htmlFor='allow_booking_modifications'>Allow Booking Modifications</Label>
|
||||||
|
</div>
|
||||||
|
<p className='text-sm text-gray-500'>Whether users can edit or cancel their bookings</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modification Time Restriction */}
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='booking_modification_hours_before'>
|
||||||
|
Modification Time Limit (hours before session)
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id='booking_modification_hours_before'
|
||||||
|
type='number'
|
||||||
|
min='0.5'
|
||||||
|
max='48'
|
||||||
|
step='0.5'
|
||||||
|
value={settings.booking_modification_hours_before}
|
||||||
|
onChange={(e) => updateSetting('booking_modification_hours_before', e.target.value)}
|
||||||
|
disabled={settings.allow_booking_modifications !== 'true'}
|
||||||
|
/>
|
||||||
|
<p className='text-sm text-gray-500'>
|
||||||
|
{settings.allow_booking_modifications === 'true'
|
||||||
|
? 'How many hours before a session users can still modify bookings'
|
||||||
|
: 'Enable booking modifications to configure this setting'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Weekend Bookings */}
|
{/* Weekend Bookings */}
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
<div className='flex items-center space-x-2'>
|
<div className='flex items-center space-x-2'>
|
||||||
@@ -307,6 +350,12 @@ export function AdminSettingsManagement() {
|
|||||||
<strong>Booking Limit:</strong> {settings.max_bookings_per_user_per_hour_per_day} per
|
<strong>Booking Limit:</strong> {settings.max_bookings_per_user_per_hour_per_day} per
|
||||||
hour
|
hour
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Booking Modifications:</strong>{' '}
|
||||||
|
{settings.allow_booking_modifications === 'true' ? 'Enabled' : 'Disabled'}
|
||||||
|
{settings.allow_booking_modifications === 'true' &&
|
||||||
|
` (${settings.booking_modification_hours_before}h before)`}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from
|
|||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Plus, Edit, Trash2, Clock } from 'lucide-react';
|
import { Plus, Edit, Trash2, Clock } from 'lucide-react';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
import { getWeekDays } from '@/lib/utils';
|
||||||
|
|
||||||
interface TimeSlot {
|
interface TimeSlot {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -21,7 +22,9 @@ interface TimeSlot {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
// Use Irish week order (Monday first)
|
||||||
|
const DAYS = getWeekDays().map((day) => day.label);
|
||||||
|
const IRISH_DAY_ORDER = [1, 2, 3, 4, 5, 6, 0]; // Monday-Sunday in JS getDay() values
|
||||||
|
|
||||||
export function AdminTimeSlotManagement() {
|
export function AdminTimeSlotManagement() {
|
||||||
const [timeSlots, setTimeSlots] = useState<TimeSlot[]>([]);
|
const [timeSlots, setTimeSlots] = useState<TimeSlot[]>([]);
|
||||||
@@ -29,7 +32,7 @@ export function AdminTimeSlotManagement() {
|
|||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
const [editingSlot, setEditingSlot] = useState<TimeSlot | null>(null);
|
const [editingSlot, setEditingSlot] = useState<TimeSlot | null>(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
dayOfWeek: 0,
|
dayOfWeek: 1, // Default to Monday (Irish standard)
|
||||||
startTime: '',
|
startTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -208,7 +211,7 @@ export function AdminTimeSlotManagement() {
|
|||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setEditingSlot(null);
|
setEditingSlot(null);
|
||||||
setFormData({
|
setFormData({
|
||||||
dayOfWeek: 0,
|
dayOfWeek: 1, // Default to Monday (Irish standard)
|
||||||
startTime: '',
|
startTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -255,9 +258,9 @@ export function AdminTimeSlotManagement() {
|
|||||||
<SelectValue placeholder='Select day' />
|
<SelectValue placeholder='Select day' />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{DAYS.map((day, index) => (
|
{IRISH_DAY_ORDER.map((jsDayOfWeek, displayIndex) => (
|
||||||
<SelectItem key={index} value={index.toString()}>
|
<SelectItem key={jsDayOfWeek} value={jsDayOfWeek.toString()}>
|
||||||
{day}
|
{DAYS[displayIndex]}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -309,16 +312,18 @@ export function AdminTimeSlotManagement() {
|
|||||||
<div className='text-center py-4'>Loading time slots...</div>
|
<div className='text-center py-4'>Loading time slots...</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='space-y-6'>
|
<div className='space-y-6'>
|
||||||
{DAYS.map((day, dayIndex) => (
|
{IRISH_DAY_ORDER.map((jsDayOfWeek, displayIndex) => {
|
||||||
<div key={dayIndex} className='space-y-2'>
|
const dayName = DAYS[displayIndex];
|
||||||
|
return (
|
||||||
|
<div key={jsDayOfWeek} className='space-y-2'>
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center'>
|
||||||
<h3 className='font-semibold text-lg'>{day}</h3>
|
<h3 className='font-semibold text-lg'>{dayName}</h3>
|
||||||
{groupedTimeSlots[dayIndex]?.length > 0 && (
|
{groupedTimeSlots[jsDayOfWeek]?.length > 0 && (
|
||||||
<Button
|
<Button
|
||||||
size='sm'
|
size='sm'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
onClick={() => handleWipeDay(dayIndex)}
|
onClick={() => handleWipeDay(jsDayOfWeek)}
|
||||||
className='text-red-600 hover:text-red-700 hover:bg-red-50'
|
className='text-destructive hover:text-destructive/80 hover:bg-destructive/10'
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<Trash2 className='h-4 w-4 mr-1' />
|
<Trash2 className='h-4 w-4 mr-1' />
|
||||||
@@ -326,17 +331,17 @@ export function AdminTimeSlotManagement() {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{groupedTimeSlots[dayIndex]?.length > 0 ? (
|
{groupedTimeSlots[jsDayOfWeek]?.length > 0 ? (
|
||||||
<div className='grid gap-2'>
|
<div className='grid gap-2'>
|
||||||
{groupedTimeSlots[dayIndex]
|
{groupedTimeSlots[jsDayOfWeek]
|
||||||
.sort((a, b) => a.startTime.localeCompare(b.startTime))
|
.sort((a, b) => a.startTime.localeCompare(b.startTime))
|
||||||
.map((slot) => (
|
.map((slot) => (
|
||||||
<div
|
<div
|
||||||
key={slot.id}
|
key={slot.id}
|
||||||
className={`flex items-center justify-between p-3 border rounded-lg ${
|
className={`flex items-center justify-between p-3 border rounded-lg ${
|
||||||
slot.isActive
|
slot.isActive
|
||||||
? 'bg-green-50 border-green-200'
|
? 'bg-green-50 border-green-200 dark:bg-green-950 dark:border-green-800'
|
||||||
: 'bg-gray-50 border-gray-200'
|
: 'bg-muted border-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className='flex items-center space-x-3'>
|
<div className='flex items-center space-x-3'>
|
||||||
@@ -346,8 +351,8 @@ export function AdminTimeSlotManagement() {
|
|||||||
<div
|
<div
|
||||||
className={`px-2 py-1 rounded-full text-xs ${
|
className={`px-2 py-1 rounded-full text-xs ${
|
||||||
slot.isActive
|
slot.isActive
|
||||||
? 'bg-green-100 text-green-800'
|
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
|
||||||
: 'bg-gray-100 text-gray-800'
|
: 'bg-muted text-muted-foreground'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{slot.isActive ? 'Active' : 'Inactive'}
|
{slot.isActive ? 'Active' : 'Inactive'}
|
||||||
@@ -365,7 +370,7 @@ export function AdminTimeSlotManagement() {
|
|||||||
size='sm'
|
size='sm'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
onClick={() => handleDelete(slot.id)}
|
onClick={() => handleDelete(slot.id)}
|
||||||
className='text-red-600 hover:text-red-700'
|
className='text-destructive hover:text-destructive/80'
|
||||||
>
|
>
|
||||||
<Trash2 className='h-4 w-4' />
|
<Trash2 className='h-4 w-4' />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -374,10 +379,13 @@ export function AdminTimeSlotManagement() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className='text-gray-500 italic'>No time slots configured for {day}</p>
|
<p className='text-muted-foreground italic'>
|
||||||
|
No time slots configured for {dayName}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -395,7 +395,7 @@ export function AdminUserManagement() {
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<Calendar className='h-4 w-4 text-gray-500' />
|
<Calendar className='h-4 w-4 text-gray-500' />
|
||||||
{new Date(user.createdAt).toLocaleDateString()}
|
{new Date(user.createdAt).toLocaleDateString('en-IE')}
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { AdminRecentBookings } from './AdminRecentBookings';
|
|||||||
import { AdminCourtManagement } from './AdminCourtManagement';
|
import { AdminCourtManagement } from './AdminCourtManagement';
|
||||||
import { AdminSettingsManagement } from './AdminSettingsManagement';
|
import { AdminSettingsManagement } from './AdminSettingsManagement';
|
||||||
import { AdminTimeSlotManagement } from './AdminTimeSlotManagement';
|
import { AdminTimeSlotManagement } from './AdminTimeSlotManagement';
|
||||||
|
import { ModeToggle } from '@/components/ui/mode-toggle';
|
||||||
|
|
||||||
interface AdminStats {
|
interface AdminStats {
|
||||||
totalUsers: number;
|
totalUsers: number;
|
||||||
@@ -74,20 +75,19 @@ export function AdminDashboard() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='min-h-screen bg-gray-50'>
|
<div className='min-h-screen bg-background'>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className='bg-white border-b border-gray-200'>
|
<header className='bg-card border-b border-border'>
|
||||||
<div className='container mx-auto px-4'>
|
<div className='container mx-auto px-4'>
|
||||||
<div className='flex items-center justify-between h-16'>
|
<div className='flex items-center justify-between h-16'>
|
||||||
<div className='flex items-center space-x-4'>
|
<div className='flex items-center space-x-4'>
|
||||||
<Shield className='h-6 w-6 text-blue-600' />
|
<Shield className='h-6 w-6 text-blue-600 dark:text-blue-400' />
|
||||||
<h1 className='text-xl font-semibold text-gray-900'>Admin Dashboard</h1>
|
<h1 className='text-xl font-semibold text-foreground'>Admin Dashboard</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center space-x-4'>
|
<div className='flex items-center space-x-4'>
|
||||||
<Badge variant='secondary' className='bg-blue-100 text-blue-800'>
|
<Badge variant='secondary'>Administrator</Badge>
|
||||||
Administrator
|
<ModeToggle />
|
||||||
</Badge>
|
|
||||||
<Button variant='ghost' size='sm' onClick={handleLogout}>
|
<Button variant='ghost' size='sm' onClick={handleLogout}>
|
||||||
<LogOut className='h-4 w-4' />
|
<LogOut className='h-4 w-4' />
|
||||||
Logout
|
Logout
|
||||||
|
|||||||
@@ -59,22 +59,22 @@ export function AnnouncementsList() {
|
|||||||
const getPriorityIcon = (priority: string) => {
|
const getPriorityIcon = (priority: string) => {
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case 'high':
|
case 'high':
|
||||||
return <AlertCircle className='h-4 w-4 text-red-500' />;
|
return <AlertCircle className='h-4 w-4 text-destructive' />;
|
||||||
case 'medium':
|
case 'medium':
|
||||||
return <AlertTriangle className='h-4 w-4 text-yellow-500' />;
|
return <AlertTriangle className='h-4 w-4 text-amber-500 dark:text-amber-400' />;
|
||||||
default:
|
default:
|
||||||
return <Info className='h-4 w-4 text-blue-500' />;
|
return <Info className='h-4 w-4 text-primary' />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPriorityColor = (priority: string) => {
|
const getPriorityColor = (priority: string) => {
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case 'high':
|
case 'high':
|
||||||
return 'bg-red-100 text-red-800 border-red-200';
|
return 'bg-destructive/10 text-destructive border-destructive/20 dark:bg-destructive/20';
|
||||||
case 'medium':
|
case 'medium':
|
||||||
return 'bg-yellow-100 text-yellow-800 border-yellow-200';
|
return 'bg-amber-100 text-amber-800 border-amber-200 dark:bg-amber-950/50 dark:text-amber-400 dark:border-amber-800/30';
|
||||||
default:
|
default:
|
||||||
return 'bg-blue-100 text-blue-800 border-blue-200';
|
return 'bg-primary/10 text-primary border-primary/20 dark:bg-primary/20';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,13 +91,15 @@ export function AnnouncementsList() {
|
|||||||
{announcements
|
{announcements
|
||||||
.filter((a) => a.isActive)
|
.filter((a) => a.isActive)
|
||||||
.map((announcement) => (
|
.map((announcement) => (
|
||||||
<div key={announcement.id} className='p-4 border rounded-lg bg-gray-50'>
|
<div key={announcement.id} className='p-4 border rounded-lg bg-card'>
|
||||||
<div className='flex items-start justify-between gap-3'>
|
<div className='flex items-start justify-between gap-3'>
|
||||||
<div className='flex items-start gap-2 flex-1'>
|
<div className='flex items-start gap-2 flex-1'>
|
||||||
{getPriorityIcon(announcement.priority)}
|
{getPriorityIcon(announcement.priority)}
|
||||||
<div className='space-y-1'>
|
<div className='space-y-1'>
|
||||||
<h4 className='font-medium text-sm'>{announcement.title}</h4>
|
<h4 className='font-medium text-sm text-foreground'>
|
||||||
<p className='text-sm text-gray-600'>{announcement.content}</p>
|
{announcement.title}
|
||||||
|
</h4>
|
||||||
|
<p className='text-sm text-muted-foreground'>{announcement.content}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge
|
<Badge
|
||||||
@@ -111,8 +113,8 @@ export function AnnouncementsList() {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{announcements.filter((a) => a.isActive).length === 0 && (
|
{announcements.filter((a) => a.isActive).length === 0 && (
|
||||||
<div className='text-center py-8 text-gray-500'>
|
<div className='text-center py-8 text-muted-foreground'>
|
||||||
<Bell className='h-8 w-8 mx-auto mb-2 opacity-30' />
|
<Bell className='h-8 w-8 mx-auto mb-2 text-muted-foreground/30' />
|
||||||
<p>No announcements at this time</p>
|
<p>No announcements at this time</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -468,24 +468,28 @@ export function EnhancedBookingCalendar() {
|
|||||||
size='sm'
|
size='sm'
|
||||||
onClick={() => setSelectedDate(date)}
|
onClick={() => setSelectedDate(date)}
|
||||||
className={`h-16 flex flex-col relative transition-all ${
|
className={`h-16 flex flex-col relative transition-all ${
|
||||||
|
isSelectedDate && !isTodayDate
|
||||||
|
? 'bg-primary text-primary-foreground hover:bg-primary/90'
|
||||||
|
: ''
|
||||||
|
} ${
|
||||||
isTodayDate && !isSelectedDate
|
isTodayDate && !isSelectedDate
|
||||||
? 'ring-2 ring-blue-400 ring-opacity-50 bg-blue-50 border-blue-200 hover:bg-blue-100'
|
? 'ring-2 ring-blue-400 ring-opacity-50 bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800 hover:bg-blue-100 dark:hover:bg-blue-900 text-foreground'
|
||||||
: ''
|
: ''
|
||||||
} ${
|
} ${
|
||||||
isSelectedDate && isTodayDate
|
isSelectedDate && isTodayDate
|
||||||
? 'bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800'
|
? 'bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 dark:from-blue-700 dark:to-blue-800 dark:hover:from-blue-800 dark:hover:to-blue-900 text-white'
|
||||||
: ''
|
: ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isTodayDate && (
|
{isTodayDate && (
|
||||||
<div className='absolute -top-1 -right-1 w-3 h-3 bg-orange-500 rounded-full animate-pulse' />
|
<div className='absolute -top-1 -right-1 w-3 h-3 bg-orange-500 dark:bg-orange-400 rounded-full animate-pulse' />
|
||||||
)}
|
)}
|
||||||
<span className='text-xs font-normal'>
|
<span className='text-xs font-normal'>
|
||||||
{date.toLocaleDateString('en-US', { weekday: 'short' })}
|
{date.toLocaleDateString('en-IE', { weekday: 'short' })}
|
||||||
</span>
|
</span>
|
||||||
<span className='font-semibold'>{date.getDate()}</span>
|
<span className='font-semibold'>{date.getDate()}</span>
|
||||||
<span className='text-xs font-normal'>
|
<span className='text-xs font-normal'>
|
||||||
{date.toLocaleDateString('en-US', { month: 'short' })}
|
{date.toLocaleDateString('en-IE', { month: 'short' })}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@@ -497,16 +501,16 @@ export function EnhancedBookingCalendar() {
|
|||||||
<div
|
<div
|
||||||
className={`text-center p-4 rounded-lg ${
|
className={`text-center p-4 rounded-lg ${
|
||||||
isToday(selectedDate)
|
isToday(selectedDate)
|
||||||
? 'bg-gradient-to-r from-blue-500 to-indigo-600 text-white'
|
? 'bg-gradient-to-r from-blue-500 to-indigo-600 text-white dark:from-blue-600 dark:to-indigo-700'
|
||||||
: 'bg-blue-50'
|
: 'bg-blue-50 dark:bg-blue-950'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<h3
|
<h3
|
||||||
className={`text-lg font-semibold ${
|
className={`text-lg font-semibold ${
|
||||||
isToday(selectedDate) ? 'text-white' : 'text-blue-900'
|
isToday(selectedDate) ? 'text-primary-foreground' : 'text-foreground'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{selectedDate.toLocaleDateString('en-US', {
|
{selectedDate.toLocaleDateString('en-IE', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
@@ -515,9 +519,9 @@ export function EnhancedBookingCalendar() {
|
|||||||
</h3>
|
</h3>
|
||||||
{isToday(selectedDate) && (
|
{isToday(selectedDate) && (
|
||||||
<div className='flex items-center justify-center gap-2 mt-2'>
|
<div className='flex items-center justify-center gap-2 mt-2'>
|
||||||
<div className='w-2 h-2 bg-orange-300 rounded-full animate-pulse' />
|
<div className='w-2 h-2 bg-orange-300 dark:bg-orange-400 rounded-full animate-pulse' />
|
||||||
<span className='text-sm font-medium text-blue-100'>Today</span>
|
<span className='text-sm font-medium text-blue-100 dark:text-blue-200'>Today</span>
|
||||||
<div className='w-2 h-2 bg-orange-300 rounded-full animate-pulse' />
|
<div className='w-2 h-2 bg-orange-300 dark:bg-orange-400 rounded-full animate-pulse' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -525,15 +529,15 @@ export function EnhancedBookingCalendar() {
|
|||||||
{/* Loading State */}
|
{/* Loading State */}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className='text-center py-8'>
|
<div className='text-center py-8'>
|
||||||
<div className='inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900'></div>
|
<div className='inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary'></div>
|
||||||
<p className='mt-2 text-sm text-gray-500'>Loading booking slots...</p>
|
<p className='mt-2 text-sm text-muted-foreground'>Loading booking slots...</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* No Courts Available */}
|
{/* No Courts Available */}
|
||||||
{!loading && courts.length === 0 && (
|
{!loading && courts.length === 0 && (
|
||||||
<div className='text-center py-8'>
|
<div className='text-center py-8'>
|
||||||
<p className='text-gray-500'>No courts available for booking</p>
|
<p className='text-muted-foreground'>No courts available for booking</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -550,7 +554,7 @@ export function EnhancedBookingCalendar() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={time} className='space-y-2'>
|
<div key={time} className='space-y-2'>
|
||||||
<div className='flex items-center gap-2 text-sm font-medium text-gray-700'>
|
<div className='flex items-center gap-2 text-sm font-medium text-foreground'>
|
||||||
<Clock className='h-4 w-4' />
|
<Clock className='h-4 w-4' />
|
||||||
{time} -{' '}
|
{time} -{' '}
|
||||||
{String(parseInt(time.split(':')[0]) + 1).padStart(2, '0')}:00
|
{String(parseInt(time.split(':')[0]) + 1).padStart(2, '0')}:00
|
||||||
@@ -565,27 +569,27 @@ export function EnhancedBookingCalendar() {
|
|||||||
{slotsForTime.map((slot) => (
|
{slotsForTime.map((slot) => (
|
||||||
<div
|
<div
|
||||||
key={`${slot.courtId}-${slot.time}`}
|
key={`${slot.courtId}-${slot.time}`}
|
||||||
className={`p-3 border rounded-lg transition-colors cursor-pointer ${
|
className={`p-3 border rounded-lg transition-all duration-200 ${
|
||||||
slot.available
|
slot.available
|
||||||
? 'border-green-200 bg-green-50 hover:bg-green-100'
|
? 'border-green-200 bg-green-50 hover:bg-green-100 hover:shadow-sm cursor-pointer dark:border-green-700 dark:bg-green-950 dark:hover:bg-green-900'
|
||||||
: 'border-red-200 bg-red-50 cursor-not-allowed'
|
: 'border-muted bg-muted/50 cursor-not-allowed opacity-75 hover:opacity-100'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleSlotClick(slot)}
|
onClick={() => handleSlotClick(slot)}
|
||||||
>
|
>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<div className='space-y-1 flex-1'>
|
<div className='space-y-1 flex-1'>
|
||||||
<div className='flex items-center gap-2 text-sm font-medium'>
|
<div className='flex items-center gap-2 text-sm font-medium text-foreground'>
|
||||||
<MapPin className='h-4 w-4' />
|
<MapPin className='h-4 w-4' />
|
||||||
{slot.courtName}
|
{slot.courtName}
|
||||||
</div>
|
</div>
|
||||||
{!slot.available && slot.bookedBy && (
|
{!slot.available && slot.bookedBy && (
|
||||||
<div className='space-y-1'>
|
<div className='space-y-1'>
|
||||||
<div className='flex items-center gap-2 text-xs text-red-600'>
|
<div className='flex items-center gap-2 text-xs text-muted-foreground'>
|
||||||
<Users className='h-3 w-3' />
|
<Users className='h-3 w-3' />
|
||||||
Booked by {slot.bookedBy}
|
Booked by {slot.bookedBy}
|
||||||
</div>
|
</div>
|
||||||
{slot.partner && (
|
{slot.partner && (
|
||||||
<div className='flex items-center gap-2 text-xs text-orange-600'>
|
<div className='flex items-center gap-2 text-xs text-orange-600 dark:text-orange-400'>
|
||||||
<User className='h-3 w-3' />
|
<User className='h-3 w-3' />
|
||||||
Playing with: {slot.partner}
|
Playing with: {slot.partner}
|
||||||
</div>
|
</div>
|
||||||
@@ -593,7 +597,7 @@ export function EnhancedBookingCalendar() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!slot.available && !slot.bookedBy && (
|
{!slot.available && !slot.bookedBy && (
|
||||||
<div className='text-xs text-red-600'>
|
<div className='text-xs text-muted-foreground'>
|
||||||
Already booked
|
Already booked
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -601,10 +605,13 @@ export function EnhancedBookingCalendar() {
|
|||||||
<Button
|
<Button
|
||||||
size='sm'
|
size='sm'
|
||||||
disabled={!slot.available}
|
disabled={!slot.available}
|
||||||
|
variant={
|
||||||
|
slot.available ? 'default' : 'secondary'
|
||||||
|
}
|
||||||
className={
|
className={
|
||||||
slot.available
|
slot.available
|
||||||
? 'bg-green-600 hover:bg-green-700'
|
? 'bg-green-600 hover:bg-green-700 dark:bg-green-700 dark:hover:bg-green-600 border-0'
|
||||||
: ''
|
: 'opacity-50 cursor-not-allowed'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{slot.available ? 'Book' : 'Booked'}
|
{slot.available ? 'Book' : 'Booked'}
|
||||||
@@ -625,16 +632,16 @@ export function EnhancedBookingCalendar() {
|
|||||||
<div className='text-center py-8'>
|
<div className='text-center py-8'>
|
||||||
{!isDayBookable() ? (
|
{!isDayBookable() ? (
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
<div className='text-red-600 font-medium'>
|
<div className='text-destructive font-medium'>
|
||||||
No courts available on {getDayName(selectedDate.getDay())}s
|
No courts available on {getDayName(selectedDate.getDay())}s
|
||||||
</div>
|
</div>
|
||||||
<p className='text-gray-500 text-sm'>
|
<p className='text-muted-foreground text-sm'>
|
||||||
This facility is closed on {getDayName(selectedDate.getDay())}s. Please
|
This facility is closed on {getDayName(selectedDate.getDay())}s. Please
|
||||||
select a different day to make a booking.
|
select a different day to make a booking.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className='text-gray-500'>No booking slots available for this date</p>
|
<p className='text-muted-foreground'>No booking slots available for this date</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -650,21 +657,21 @@ export function EnhancedBookingCalendar() {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
{selectedSlot && (
|
{selectedSlot && (
|
||||||
<div className='bg-blue-50 p-4 rounded-lg space-y-2'>
|
<div className='bg-primary/5 border border-primary/20 p-4 rounded-lg space-y-2 dark:bg-primary/10 dark:border-primary/30'>
|
||||||
<div className='flex items-center gap-2 text-sm'>
|
<div className='flex items-center gap-2 text-sm text-foreground'>
|
||||||
<Calendar className='h-4 w-4' />
|
<Calendar className='h-4 w-4' />
|
||||||
{selectedDate.toLocaleDateString('en-US', {
|
{selectedDate.toLocaleDateString('en-IE', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 text-sm'>
|
<div className='flex items-center gap-2 text-sm text-foreground'>
|
||||||
<Clock className='h-4 w-4' />
|
<Clock className='h-4 w-4' />
|
||||||
{selectedSlot.time} -{' '}
|
{selectedSlot.time} -{' '}
|
||||||
{String(parseInt(selectedSlot.time.split(':')[0]) + 1).padStart(2, '0')}:00
|
{String(parseInt(selectedSlot.time.split(':')[0]) + 1).padStart(2, '0')}:00
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2 text-sm'>
|
<div className='flex items-center gap-2 text-sm text-foreground'>
|
||||||
<MapPin className='h-4 w-4' />
|
<MapPin className='h-4 w-4' />
|
||||||
{selectedSlot.courtName}
|
{selectedSlot.courtName}
|
||||||
</div>
|
</div>
|
||||||
@@ -674,7 +681,7 @@ export function EnhancedBookingCalendar() {
|
|||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
<Label htmlFor='partner'>Playing Partner (Optional)</Label>
|
<Label htmlFor='partner'>Playing Partner (Optional)</Label>
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<User className='absolute left-3 top-3 h-4 w-4 text-gray-400' />
|
<User className='absolute left-3 top-3 h-4 w-4 text-muted-foreground' />
|
||||||
<Input
|
<Input
|
||||||
id='partner'
|
id='partner'
|
||||||
placeholder='Who will you be playing with?'
|
placeholder='Who will you be playing with?'
|
||||||
@@ -683,7 +690,9 @@ export function EnhancedBookingCalendar() {
|
|||||||
className='pl-10'
|
className='pl-10'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className='text-xs text-gray-500'>Enter the name of the person you'll be playing with</p>
|
<p className='text-xs text-muted-foreground'>
|
||||||
|
Enter the name of the person you'll be playing with
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
|
|||||||
@@ -44,10 +44,15 @@ export function UserBookingManagement() {
|
|||||||
const [selectedBooking, setSelectedBooking] = useState<Booking | null>(null);
|
const [selectedBooking, setSelectedBooking] = useState<Booking | null>(null);
|
||||||
const [editNotes, setEditNotes] = useState('');
|
const [editNotes, setEditNotes] = useState('');
|
||||||
const [editPartner, setEditPartner] = useState('');
|
const [editPartner, setEditPartner] = useState('');
|
||||||
|
const [settings, setSettings] = useState<{
|
||||||
|
allow_booking_modifications: string;
|
||||||
|
booking_modification_hours_before: string;
|
||||||
|
} | null>(null);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchBookings();
|
fetchBookings();
|
||||||
|
fetchSettings();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchBookings = async () => {
|
const fetchBookings = async () => {
|
||||||
@@ -79,6 +84,34 @@ export function UserBookingManagement() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchSettings = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/settings');
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const settingsMap = {
|
||||||
|
allow_booking_modifications: 'true',
|
||||||
|
booking_modification_hours_before: '2',
|
||||||
|
};
|
||||||
|
|
||||||
|
data.settings?.forEach((setting: any) => {
|
||||||
|
if (setting.key in settingsMap) {
|
||||||
|
settingsMap[setting.key as keyof typeof settingsMap] = setting.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setSettings(settingsMap);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching settings:', error);
|
||||||
|
// Use default settings if fetch fails
|
||||||
|
setSettings({
|
||||||
|
allow_booking_modifications: 'true',
|
||||||
|
booking_modification_hours_before: '2',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const parseBookingNotes = (notes?: string) => {
|
const parseBookingNotes = (notes?: string) => {
|
||||||
if (!notes) return { partner: '', additionalNotes: '' };
|
if (!notes) return { partner: '', additionalNotes: '' };
|
||||||
|
|
||||||
@@ -205,13 +238,19 @@ export function UserBookingManagement() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const canModifyBooking = (booking: Booking) => {
|
const canModifyBooking = (booking: Booking) => {
|
||||||
|
if (!settings || settings.allow_booking_modifications !== 'true') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const bookingDateTime = new Date(`${booking.date}T${booking.startTime}`);
|
const bookingDateTime = new Date(`${booking.date}T${booking.startTime}`);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const timeDiff = bookingDateTime.getTime() - now.getTime();
|
const timeDiff = bookingDateTime.getTime() - now.getTime();
|
||||||
const hoursDiff = timeDiff / (1000 * 60 * 60);
|
const hoursDiff = timeDiff / (1000 * 60 * 60);
|
||||||
|
|
||||||
// Allow modifications if booking is more than 2 hours away
|
const requiredHours = parseFloat(settings.booking_modification_hours_before) || 2;
|
||||||
return hoursDiff > 2;
|
|
||||||
|
// Allow modifications if booking is more than the required hours away
|
||||||
|
return hoursDiff > requiredHours;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -227,8 +266,8 @@ export function UserBookingManagement() {
|
|||||||
<div className='space-y-3'>
|
<div className='space-y-3'>
|
||||||
{[1, 2, 3].map((i) => (
|
{[1, 2, 3].map((i) => (
|
||||||
<div key={i} className='animate-pulse border rounded-lg p-4'>
|
<div key={i} className='animate-pulse border rounded-lg p-4'>
|
||||||
<div className='h-4 bg-gray-200 rounded w-3/4 mb-2'></div>
|
<div className='h-4 bg-muted rounded w-3/4 mb-2'></div>
|
||||||
<div className='h-3 bg-gray-200 rounded w-1/2'></div>
|
<div className='h-3 bg-muted rounded w-1/2'></div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -251,7 +290,7 @@ export function UserBookingManagement() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{bookings.length === 0 ? (
|
{bookings.length === 0 ? (
|
||||||
<div className='text-sm text-gray-500 text-center py-6'>
|
<div className='text-sm text-muted-foreground text-center py-6'>
|
||||||
No upcoming bookings. Make your first booking!
|
No upcoming bookings. Make your first booking!
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -265,19 +304,19 @@ export function UserBookingManagement() {
|
|||||||
<div className='flex items-start justify-between'>
|
<div className='flex items-start justify-between'>
|
||||||
<div className='space-y-2 flex-1'>
|
<div className='space-y-2 flex-1'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<MapPin className='h-4 w-4 text-blue-600' />
|
<MapPin className='h-4 w-4 text-primary' />
|
||||||
<span className='font-medium text-sm'>{booking.court.name}</span>
|
<span className='font-medium text-sm'>{booking.court.name}</span>
|
||||||
{isToday(booking.date) && (
|
{isToday(booking.date) && (
|
||||||
<Badge
|
<Badge
|
||||||
variant='secondary'
|
variant='secondary'
|
||||||
className='text-xs bg-gradient-to-r from-orange-100 to-orange-200 text-orange-700 border-orange-300'
|
className='text-xs bg-orange-100 text-orange-700 border-orange-300 dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800'
|
||||||
>
|
>
|
||||||
🎯 Today
|
🎯 Today
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center gap-4 text-xs text-gray-500'>
|
<div className='flex items-center gap-4 text-xs text-muted-foreground'>
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
<Calendar className='h-3 w-3' />
|
<Calendar className='h-3 w-3' />
|
||||||
<span>{formatDate(booking.date)}</span>
|
<span>{formatDate(booking.date)}</span>
|
||||||
@@ -291,14 +330,14 @@ export function UserBookingManagement() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{partner && (
|
{partner && (
|
||||||
<div className='flex items-center gap-1 text-xs text-gray-600'>
|
<div className='flex items-center gap-1 text-xs text-muted-foreground'>
|
||||||
<User className='h-3 w-3' />
|
<User className='h-3 w-3' />
|
||||||
<span>Playing with: {partner}</span>
|
<span>Playing with: {partner}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{additionalNotes && (
|
{additionalNotes && (
|
||||||
<p className='text-xs text-gray-600 italic bg-gray-50 p-2 rounded'>
|
<p className='text-xs text-muted-foreground italic bg-muted p-2 rounded'>
|
||||||
{additionalNotes}
|
{additionalNotes}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -319,7 +358,7 @@ export function UserBookingManagement() {
|
|||||||
variant='outline'
|
variant='outline'
|
||||||
onClick={() => handleDeleteClick(booking)}
|
onClick={() => handleDeleteClick(booking)}
|
||||||
disabled={!canModify}
|
disabled={!canModify}
|
||||||
className='h-8 w-8 p-0 text-red-600 hover:text-red-700'
|
className='h-8 w-8 p-0 text-destructive hover:text-destructive/80'
|
||||||
>
|
>
|
||||||
<Trash2 className='h-3 w-3' />
|
<Trash2 className='h-3 w-3' />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -327,8 +366,12 @@ export function UserBookingManagement() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!canModify && (
|
{!canModify && (
|
||||||
<p className='text-xs text-amber-600 bg-amber-50 p-2 rounded'>
|
<p className='text-xs text-amber-600 bg-amber-50 p-2 rounded dark:text-amber-400 dark:bg-amber-950'>
|
||||||
Booking can only be modified more than 2 hours before the session
|
{settings?.allow_booking_modifications !== 'true'
|
||||||
|
? 'Booking modifications are currently disabled by administrator'
|
||||||
|
: `Booking can only be modified more than ${
|
||||||
|
settings?.booking_modification_hours_before || '2'
|
||||||
|
} hours before the session`}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -347,7 +390,7 @@ export function UserBookingManagement() {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
{selectedBooking && (
|
{selectedBooking && (
|
||||||
<div className='bg-blue-50 p-4 rounded-lg space-y-2'>
|
<div className='bg-blue-50 p-4 rounded-lg space-y-2 dark:bg-blue-950'>
|
||||||
<div className='flex items-center gap-2 text-sm'>
|
<div className='flex items-center gap-2 text-sm'>
|
||||||
<Calendar className='h-4 w-4' />
|
<Calendar className='h-4 w-4' />
|
||||||
{formatDate(selectedBooking.date)}
|
{formatDate(selectedBooking.date)}
|
||||||
@@ -366,7 +409,7 @@ export function UserBookingManagement() {
|
|||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
<Label htmlFor='edit-partner'>Playing Partner</Label>
|
<Label htmlFor='edit-partner'>Playing Partner</Label>
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<User className='absolute left-3 top-3 h-4 w-4 text-gray-400' />
|
<User className='absolute left-3 top-3 h-4 w-4 text-muted-foreground' />
|
||||||
<Input
|
<Input
|
||||||
id='edit-partner'
|
id='edit-partner'
|
||||||
placeholder='Who will you be playing with?'
|
placeholder='Who will you be playing with?'
|
||||||
@@ -408,11 +451,11 @@ export function UserBookingManagement() {
|
|||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
Are you sure you want to cancel this booking? This action cannot be undone.
|
Are you sure you want to cancel this booking? This action cannot be undone.
|
||||||
{selectedBooking && (
|
{selectedBooking && (
|
||||||
<div className='mt-3 p-3 bg-gray-50 rounded'>
|
<div className='mt-3 p-3 bg-muted rounded'>
|
||||||
<p className='text-sm font-medium'>
|
<p className='text-sm font-medium'>
|
||||||
{selectedBooking.court.name} - {formatDate(selectedBooking.date)}
|
{selectedBooking.court.name} - {formatDate(selectedBooking.date)}
|
||||||
</p>
|
</p>
|
||||||
<p className='text-sm text-gray-600'>
|
<p className='text-sm text-muted-foreground'>
|
||||||
{selectedBooking.startTime} - {selectedBooking.endTime}
|
{selectedBooking.startTime} - {selectedBooking.endTime}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -421,7 +464,10 @@ export function UserBookingManagement() {
|
|||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>Keep Booking</AlertDialogCancel>
|
<AlertDialogCancel>Keep Booking</AlertDialogCancel>
|
||||||
<AlertDialogAction onClick={handleDeleteConfirm} className='bg-red-600 hover:bg-red-700'>
|
<AlertDialogAction
|
||||||
|
onClick={handleDeleteConfirm}
|
||||||
|
className='bg-destructive hover:bg-destructive/90'
|
||||||
|
>
|
||||||
Cancel Booking
|
Cancel Booking
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function BookingCalendar() {
|
|||||||
const [selectedCourt, setSelectedCourt] = useState<string | null>(null);
|
const [selectedCourt, setSelectedCourt] = useState<string | null>(null);
|
||||||
|
|
||||||
const formatDate = (date: Date) => {
|
const formatDate = (date: Date) => {
|
||||||
return date.toLocaleDateString('en-US', {
|
return date.toLocaleDateString('en-IE', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
@@ -145,13 +145,17 @@ export function BookingCalendar() {
|
|||||||
size='sm'
|
size='sm'
|
||||||
onClick={() => !isBooked && setSelectedSlot(time)}
|
onClick={() => !isBooked && setSelectedSlot(time)}
|
||||||
disabled={isBooked}
|
disabled={isBooked}
|
||||||
className='relative'
|
className={`relative transition-all ${
|
||||||
|
isBooked
|
||||||
|
? 'opacity-60 cursor-not-allowed bg-muted/50 border-muted text-muted-foreground hover:opacity-75'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{time}
|
{time}
|
||||||
{isBooked && (
|
{isBooked && (
|
||||||
<Badge
|
<Badge
|
||||||
variant='destructive'
|
variant='secondary'
|
||||||
className='absolute -top-1 -right-1 h-2 w-2 p-0'
|
className='absolute -top-1 -right-1 h-2 w-2 p-0 bg-muted border-muted'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -162,9 +166,9 @@ export function BookingCalendar() {
|
|||||||
|
|
||||||
{/* Booking Summary */}
|
{/* Booking Summary */}
|
||||||
{selectedDate && selectedSlot && selectedCourt && (
|
{selectedDate && selectedSlot && selectedCourt && (
|
||||||
<div className='bg-blue-50 border border-blue-200 rounded-lg p-4 space-y-2'>
|
<div className='bg-primary/5 border border-primary/20 rounded-lg p-4 space-y-2 dark:bg-primary/10 dark:border-primary/30'>
|
||||||
<h4 className='font-medium text-blue-900'>Booking Summary</h4>
|
<h4 className='font-medium text-primary dark:text-primary-foreground'>Booking Summary</h4>
|
||||||
<div className='text-sm text-blue-700 space-y-1'>
|
<div className='text-sm text-primary/80 dark:text-primary-foreground/80 space-y-1'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<CalendarIcon className='h-3 w-3' />
|
<CalendarIcon className='h-3 w-3' />
|
||||||
{formatDate(selectedDate)}
|
{formatDate(selectedDate)}
|
||||||
|
|||||||
@@ -85,16 +85,16 @@ export function RecentBookings() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{bookings.length === 0 ? (
|
{bookings.length === 0 ? (
|
||||||
<div className='text-sm text-gray-500 text-center py-6'>
|
<div className='text-sm text-muted-foreground text-center py-6'>
|
||||||
No recent bookings yet. Make your first booking!
|
No recent bookings yet. Make your first booking!
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='space-y-3'>
|
<div className='space-y-3'>
|
||||||
{bookings.map((booking) => (
|
{bookings.map((booking) => (
|
||||||
<div key={booking.id} className='border rounded-lg p-3 space-y-2'>
|
<div key={booking.id} className='border border-border rounded-lg p-3 space-y-2'>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<MapPin className='h-4 w-4 text-blue-600' />
|
<MapPin className='h-4 w-4 text-primary' />
|
||||||
<span className='font-medium text-sm'>{booking.court.name}</span>
|
<span className='font-medium text-sm'>{booking.court.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant={booking.status === 'active' ? 'default' : 'secondary'}>
|
<Badge variant={booking.status === 'active' ? 'default' : 'secondary'}>
|
||||||
@@ -102,7 +102,7 @@ export function RecentBookings() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center gap-4 text-xs text-gray-500'>
|
<div className='flex items-center gap-4 text-xs text-muted-foreground'>
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
<Calendar className='h-3 w-3' />
|
<Calendar className='h-3 w-3' />
|
||||||
<span>{formatDate(booking.date)}</span>
|
<span>{formatDate(booking.date)}</span>
|
||||||
@@ -115,7 +115,9 @@ export function RecentBookings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{booking.notes && <p className='text-xs text-gray-600 italic'>{booking.notes}</p>}
|
{booking.notes && (
|
||||||
|
<p className='text-xs text-muted-foreground italic'>{booking.notes}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Bell, LogOut, Settings, User, Calendar } from 'lucide-react';
|
|||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { NotificationBell, AnnouncementsModal } from '@/components/notifications/announcements';
|
import { NotificationBell, AnnouncementsModal } from '@/components/notifications/announcements';
|
||||||
import { UserProfile } from '@/components/user/user-profile';
|
import { UserProfile } from '@/components/user/user-profile';
|
||||||
|
import { ModeToggle } from '@/components/ui/mode-toggle';
|
||||||
|
|
||||||
interface DashboardHeaderProps {
|
interface DashboardHeaderProps {
|
||||||
user: {
|
user: {
|
||||||
@@ -71,24 +72,22 @@ export function DashboardHeader({ user }: DashboardHeaderProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className='bg-white/80 backdrop-blur-md border-b border-gray-200 sticky top-0 z-50'>
|
<header className='bg-background/80 backdrop-blur-md border-b border-border sticky top-0 z-50'>
|
||||||
<div className='container mx-auto px-4'>
|
<div className='container mx-auto px-4'>
|
||||||
<div className='flex items-center justify-between h-16'>
|
<div className='flex items-center justify-between h-16'>
|
||||||
<div className='flex items-center space-x-4'>
|
<div className='flex items-center space-x-4'>
|
||||||
<div className='flex items-center space-x-2'>
|
<div className='flex items-center space-x-2'>
|
||||||
<Calendar className='h-6 w-6 text-blue-600' />
|
<Calendar className='h-6 w-6 text-primary' />
|
||||||
<h1 className='text-xl font-bold text-gray-900'>TT Booking</h1>
|
<h1 className='text-xl font-bold text-foreground'>TT Booking</h1>
|
||||||
</div>
|
</div>
|
||||||
{user.role === 'admin' && (
|
{user.role === 'admin' && <Badge variant='secondary'>Admin</Badge>}
|
||||||
<Badge variant='secondary' className='bg-purple-100 text-purple-800'>
|
|
||||||
Admin
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center space-x-4'>
|
<div className='flex items-center space-x-4'>
|
||||||
<NotificationBell unreadCount={unreadCount} onClick={() => setShowAnnouncements(true)} />
|
<NotificationBell unreadCount={unreadCount} onClick={() => setShowAnnouncements(true)} />
|
||||||
|
|
||||||
|
<ModeToggle />
|
||||||
|
|
||||||
{user.role === 'admin' && (
|
{user.role === 'admin' && (
|
||||||
<Button variant='ghost' size='sm' onClick={() => router.push('/admin')}>
|
<Button variant='ghost' size='sm' onClick={() => router.push('/admin')}>
|
||||||
<Settings className='h-4 w-4 mr-2' />
|
<Settings className='h-4 w-4 mr-2' />
|
||||||
@@ -102,8 +101,8 @@ export function DashboardHeader({ user }: DashboardHeaderProps) {
|
|||||||
onClick={() => setShowUserProfile(true)}
|
onClick={() => setShowUserProfile(true)}
|
||||||
className='flex items-center space-x-2'
|
className='flex items-center space-x-2'
|
||||||
>
|
>
|
||||||
<User className='h-4 w-4 text-gray-600' />
|
<User className='h-4 w-4 text-muted-foreground' />
|
||||||
<span className='text-sm text-gray-700'>
|
<span className='text-sm text-foreground'>
|
||||||
{user.name && user.surname ? `${user.name} ${user.surname}` : user.email.split('@')[0]}
|
{user.name && user.surname ? `${user.name} ${user.surname}` : user.email.split('@')[0]}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -65,27 +65,27 @@ export function AnnouncementsModal({ isOpen, onClose, unreadCount, onCountUpdate
|
|||||||
const getPriorityIcon = (priority: string) => {
|
const getPriorityIcon = (priority: string) => {
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case 'high':
|
case 'high':
|
||||||
return <AlertCircle className='h-4 w-4 text-red-500' />;
|
return <AlertCircle className='h-4 w-4 text-destructive' />;
|
||||||
case 'medium':
|
case 'medium':
|
||||||
return <AlertTriangle className='h-4 w-4 text-yellow-500' />;
|
return <AlertTriangle className='h-4 w-4 text-amber-500 dark:text-amber-400' />;
|
||||||
default:
|
default:
|
||||||
return <Info className='h-4 w-4 text-blue-500' />;
|
return <Info className='h-4 w-4 text-primary' />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPriorityColor = (priority: string) => {
|
const getPriorityColor = (priority: string) => {
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case 'high':
|
case 'high':
|
||||||
return 'border-red-200 bg-red-50';
|
return 'border-destructive/20 bg-destructive/5';
|
||||||
case 'medium':
|
case 'medium':
|
||||||
return 'border-yellow-200 bg-yellow-50';
|
return 'border-amber-500/20 bg-amber-500/5 dark:border-amber-400/20 dark:bg-amber-400/5';
|
||||||
default:
|
default:
|
||||||
return 'border-blue-200 bg-blue-50';
|
return 'border-primary/20 bg-primary/5';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (dateStr: string) => {
|
const formatDate = (dateStr: string) => {
|
||||||
return new Date(dateStr).toLocaleDateString('en-US', {
|
return new Date(dateStr).toLocaleDateString('en-IE', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
@@ -112,12 +112,12 @@ export function AnnouncementsModal({ isOpen, onClose, unreadCount, onCountUpdate
|
|||||||
<div className='flex-1 overflow-y-auto'>
|
<div className='flex-1 overflow-y-auto'>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className='flex items-center justify-center py-8'>
|
<div className='flex items-center justify-center py-8'>
|
||||||
<div className='animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900'></div>
|
<div className='animate-spin rounded-full h-8 w-8 border-b-2 border-foreground'></div>
|
||||||
<p className='ml-2'>Loading announcements...</p>
|
<p className='ml-2 text-foreground'>Loading announcements...</p>
|
||||||
</div>
|
</div>
|
||||||
) : announcements.length === 0 ? (
|
) : announcements.length === 0 ? (
|
||||||
<div className='text-center py-8 text-gray-500'>
|
<div className='text-center py-8 text-muted-foreground'>
|
||||||
<Bell className='h-12 w-12 mx-auto mb-4 text-gray-300' />
|
<Bell className='h-12 w-12 mx-auto mb-4 text-muted-foreground/50' />
|
||||||
<p>No announcements at this time</p>
|
<p>No announcements at this time</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -137,8 +137,10 @@ export function AnnouncementsModal({ isOpen, onClose, unreadCount, onCountUpdate
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='pt-0'>
|
<CardContent className='pt-0'>
|
||||||
<p className='text-sm text-gray-700 mb-2'>{announcement.content}</p>
|
<p className='text-sm text-foreground mb-2'>{announcement.content}</p>
|
||||||
<p className='text-xs text-gray-500'>{formatDate(announcement.createdAt)}</p>
|
<p className='text-xs text-muted-foreground'>
|
||||||
|
{formatDate(announcement.createdAt)}
|
||||||
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -7,3 +7,50 @@ import { type ThemeProviderProps } from 'next-themes/dist/types';
|
|||||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enhanced hook for theme management with database sync
|
||||||
|
export function useThemeWithSync() {
|
||||||
|
const [mounted, setMounted] = React.useState(false);
|
||||||
|
const [userTheme, setUserTheme] = React.useState<'light' | 'dark' | 'system'>('system');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
fetchUserTheme();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchUserTheme = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/users/theme');
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setUserTheme(data.themePreference);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch user theme preference:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTheme = async (theme: 'light' | 'dark' | 'system') => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/users/theme', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ themePreference: theme }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setUserTheme(theme);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update theme preference:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
mounted,
|
||||||
|
theme: userTheme,
|
||||||
|
setTheme: updateTheme,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
+62
-111
@@ -1,213 +1,164 @@
|
|||||||
"use client"
|
'use client';
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from 'react';
|
||||||
import {
|
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
|
||||||
ChevronDownIcon,
|
import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker';
|
||||||
ChevronLeftIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils';
|
||||||
import { Button, buttonVariants } from "@/components/ui/button"
|
import { Button, buttonVariants } from '@/components/ui/button';
|
||||||
|
|
||||||
function Calendar({
|
function Calendar({
|
||||||
className,
|
className,
|
||||||
classNames,
|
classNames,
|
||||||
showOutsideDays = true,
|
showOutsideDays = true,
|
||||||
captionLayout = "label",
|
captionLayout = 'label',
|
||||||
buttonVariant = "ghost",
|
buttonVariant = 'ghost',
|
||||||
formatters,
|
formatters,
|
||||||
components,
|
components,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DayPicker> & {
|
}: React.ComponentProps<typeof DayPicker> & {
|
||||||
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
buttonVariant?: React.ComponentProps<typeof Button>['variant'];
|
||||||
}) {
|
}) {
|
||||||
const defaultClassNames = getDefaultClassNames()
|
const defaultClassNames = getDefaultClassNames();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DayPicker
|
<DayPicker
|
||||||
|
weekStartsOn={1} // Monday as first day for Ireland
|
||||||
showOutsideDays={showOutsideDays}
|
showOutsideDays={showOutsideDays}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
'bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent',
|
||||||
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||||
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
captionLayout={captionLayout}
|
captionLayout={captionLayout}
|
||||||
formatters={{
|
formatters={{
|
||||||
formatMonthDropdown: (date) =>
|
formatMonthDropdown: (date) => date.toLocaleString('en-IE', { month: 'short' }),
|
||||||
date.toLocaleString("default", { month: "short" }),
|
|
||||||
...formatters,
|
...formatters,
|
||||||
}}
|
}}
|
||||||
classNames={{
|
classNames={{
|
||||||
root: cn("w-fit", defaultClassNames.root),
|
root: cn('w-fit', defaultClassNames.root),
|
||||||
months: cn(
|
months: cn('relative flex flex-col gap-4 md:flex-row', defaultClassNames.months),
|
||||||
"relative flex flex-col gap-4 md:flex-row",
|
month: cn('flex w-full flex-col gap-4', defaultClassNames.month),
|
||||||
defaultClassNames.months
|
|
||||||
),
|
|
||||||
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
|
|
||||||
nav: cn(
|
nav: cn(
|
||||||
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
|
'absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1',
|
||||||
defaultClassNames.nav
|
defaultClassNames.nav
|
||||||
),
|
),
|
||||||
button_previous: cn(
|
button_previous: cn(
|
||||||
buttonVariants({ variant: buttonVariant }),
|
buttonVariants({ variant: buttonVariant }),
|
||||||
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
|
'h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50',
|
||||||
defaultClassNames.button_previous
|
defaultClassNames.button_previous
|
||||||
),
|
),
|
||||||
button_next: cn(
|
button_next: cn(
|
||||||
buttonVariants({ variant: buttonVariant }),
|
buttonVariants({ variant: buttonVariant }),
|
||||||
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
|
'h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50',
|
||||||
defaultClassNames.button_next
|
defaultClassNames.button_next
|
||||||
),
|
),
|
||||||
month_caption: cn(
|
month_caption: cn(
|
||||||
"flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
|
'flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]',
|
||||||
defaultClassNames.month_caption
|
defaultClassNames.month_caption
|
||||||
),
|
),
|
||||||
dropdowns: cn(
|
dropdowns: cn(
|
||||||
"flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
|
'flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium',
|
||||||
defaultClassNames.dropdowns
|
defaultClassNames.dropdowns
|
||||||
),
|
),
|
||||||
dropdown_root: cn(
|
dropdown_root: cn(
|
||||||
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
|
'has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border',
|
||||||
defaultClassNames.dropdown_root
|
defaultClassNames.dropdown_root
|
||||||
),
|
),
|
||||||
dropdown: cn(
|
dropdown: cn('bg-popover absolute inset-0 opacity-0', defaultClassNames.dropdown),
|
||||||
"bg-popover absolute inset-0 opacity-0",
|
|
||||||
defaultClassNames.dropdown
|
|
||||||
),
|
|
||||||
caption_label: cn(
|
caption_label: cn(
|
||||||
"select-none font-medium",
|
'select-none font-medium',
|
||||||
captionLayout === "label"
|
captionLayout === 'label'
|
||||||
? "text-sm"
|
? 'text-sm'
|
||||||
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
|
: '[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5',
|
||||||
defaultClassNames.caption_label
|
defaultClassNames.caption_label
|
||||||
),
|
),
|
||||||
table: "w-full border-collapse",
|
table: 'w-full border-collapse',
|
||||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
weekdays: cn('flex', defaultClassNames.weekdays),
|
||||||
weekday: cn(
|
weekday: cn(
|
||||||
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
|
'text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal',
|
||||||
defaultClassNames.weekday
|
defaultClassNames.weekday
|
||||||
),
|
),
|
||||||
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
week: cn('mt-2 flex w-full', defaultClassNames.week),
|
||||||
week_number_header: cn(
|
week_number_header: cn('w-[--cell-size] select-none', defaultClassNames.week_number_header),
|
||||||
"w-[--cell-size] select-none",
|
week_number: cn('text-muted-foreground select-none text-[0.8rem]', defaultClassNames.week_number),
|
||||||
defaultClassNames.week_number_header
|
|
||||||
),
|
|
||||||
week_number: cn(
|
|
||||||
"text-muted-foreground select-none text-[0.8rem]",
|
|
||||||
defaultClassNames.week_number
|
|
||||||
),
|
|
||||||
day: cn(
|
day: cn(
|
||||||
"group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
|
'group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md',
|
||||||
defaultClassNames.day
|
defaultClassNames.day
|
||||||
),
|
),
|
||||||
range_start: cn(
|
range_start: cn('bg-accent rounded-l-md', defaultClassNames.range_start),
|
||||||
"bg-accent rounded-l-md",
|
range_middle: cn('rounded-none', defaultClassNames.range_middle),
|
||||||
defaultClassNames.range_start
|
range_end: cn('bg-accent rounded-r-md', defaultClassNames.range_end),
|
||||||
),
|
|
||||||
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
|
||||||
range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
|
|
||||||
today: cn(
|
today: cn(
|
||||||
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
|
'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
|
||||||
defaultClassNames.today
|
defaultClassNames.today
|
||||||
),
|
),
|
||||||
outside: cn(
|
outside: cn('text-muted-foreground aria-selected:text-muted-foreground', defaultClassNames.outside),
|
||||||
"text-muted-foreground aria-selected:text-muted-foreground",
|
disabled: cn('text-muted-foreground opacity-50', defaultClassNames.disabled),
|
||||||
defaultClassNames.outside
|
hidden: cn('invisible', defaultClassNames.hidden),
|
||||||
),
|
|
||||||
disabled: cn(
|
|
||||||
"text-muted-foreground opacity-50",
|
|
||||||
defaultClassNames.disabled
|
|
||||||
),
|
|
||||||
hidden: cn("invisible", defaultClassNames.hidden),
|
|
||||||
...classNames,
|
...classNames,
|
||||||
}}
|
}}
|
||||||
components={{
|
components={{
|
||||||
Root: ({ className, rootRef, ...props }) => {
|
Root: ({ className, rootRef, ...props }) => {
|
||||||
return (
|
return <div data-slot='calendar' ref={rootRef} className={cn(className)} {...props} />;
|
||||||
<div
|
|
||||||
data-slot="calendar"
|
|
||||||
ref={rootRef}
|
|
||||||
className={cn(className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
Chevron: ({ className, orientation, ...props }) => {
|
Chevron: ({ className, orientation, ...props }) => {
|
||||||
if (orientation === "left") {
|
if (orientation === 'left') {
|
||||||
return (
|
return <ChevronLeftIcon className={cn('size-4', className)} {...props} />;
|
||||||
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orientation === "right") {
|
if (orientation === 'right') {
|
||||||
return (
|
return <ChevronRightIcon className={cn('size-4', className)} {...props} />;
|
||||||
<ChevronRightIcon
|
|
||||||
className={cn("size-4", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <ChevronDownIcon className={cn('size-4', className)} {...props} />;
|
||||||
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
DayButton: CalendarDayButton,
|
DayButton: CalendarDayButton,
|
||||||
WeekNumber: ({ children, ...props }) => {
|
WeekNumber: ({ children, ...props }) => {
|
||||||
return (
|
return (
|
||||||
<td {...props}>
|
<td {...props}>
|
||||||
<div className="flex size-[--cell-size] items-center justify-center text-center">
|
<div className='flex size-[--cell-size] items-center justify-center text-center'>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
...components,
|
...components,
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CalendarDayButton({
|
function CalendarDayButton({ className, day, modifiers, ...props }: React.ComponentProps<typeof DayButton>) {
|
||||||
className,
|
const defaultClassNames = getDefaultClassNames();
|
||||||
day,
|
|
||||||
modifiers,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DayButton>) {
|
|
||||||
const defaultClassNames = getDefaultClassNames()
|
|
||||||
|
|
||||||
const ref = React.useRef<HTMLButtonElement>(null)
|
const ref = React.useRef<HTMLButtonElement>(null);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (modifiers.focused) ref.current?.focus()
|
if (modifiers.focused) ref.current?.focus();
|
||||||
}, [modifiers.focused])
|
}, [modifiers.focused]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
ref={ref}
|
ref={ref}
|
||||||
variant="ghost"
|
variant='ghost'
|
||||||
size="icon"
|
size='icon'
|
||||||
data-day={day.date.toLocaleDateString()}
|
data-day={day.date.toLocaleDateString('en-IE')}
|
||||||
data-selected-single={
|
data-selected-single={
|
||||||
modifiers.selected &&
|
modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle
|
||||||
!modifiers.range_start &&
|
|
||||||
!modifiers.range_end &&
|
|
||||||
!modifiers.range_middle
|
|
||||||
}
|
}
|
||||||
data-range-start={modifiers.range_start}
|
data-range-start={modifiers.range_start}
|
||||||
data-range-end={modifiers.range_end}
|
data-range-end={modifiers.range_end}
|
||||||
data-range-middle={modifiers.range_middle}
|
data-range-middle={modifiers.range_middle}
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
|
'data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70',
|
||||||
defaultClassNames.day,
|
defaultClassNames.day,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Calendar, CalendarDayButton }
|
export { Calendar, CalendarDayButton };
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||||
|
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||||
|
|
||||||
|
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||||
|
|
||||||
|
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||||
|
|
||||||
|
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||||
|
|
||||||
|
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||||
|
|
||||||
|
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||||
|
|
||||||
|
const DropdownMenuSubTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, children, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronRight className="ml-auto" />
|
||||||
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
|
))
|
||||||
|
DropdownMenuSubTrigger.displayName =
|
||||||
|
DropdownMenuPrimitive.SubTrigger.displayName
|
||||||
|
|
||||||
|
const DropdownMenuSubContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.SubContent
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuSubContent.displayName =
|
||||||
|
DropdownMenuPrimitive.SubContent.displayName
|
||||||
|
|
||||||
|
const DropdownMenuContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Portal>
|
||||||
|
<DropdownMenuPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</DropdownMenuPrimitive.Portal>
|
||||||
|
))
|
||||||
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const DropdownMenuItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||||
|
>(({ className, children, checked, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
checked={checked}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
|
))
|
||||||
|
DropdownMenuCheckboxItem.displayName =
|
||||||
|
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||||
|
|
||||||
|
const DropdownMenuRadioItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.RadioItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<Circle className="h-2 w-2 fill-current" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
|
))
|
||||||
|
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||||
|
|
||||||
|
const DropdownMenuLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"px-2 py-1.5 text-sm font-semibold",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const DropdownMenuSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const DropdownMenuShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuShortcut,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuPortal,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Moon, Sun } from 'lucide-react';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
|
||||||
|
export function ModeToggle() {
|
||||||
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
|
const handleThemeChange = async (newTheme: 'light' | 'dark' | 'system') => {
|
||||||
|
// Update next-themes immediately for UI responsiveness
|
||||||
|
setTheme(newTheme);
|
||||||
|
|
||||||
|
// Sync with database in background
|
||||||
|
try {
|
||||||
|
await fetch('/api/users/theme', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ themePreference: newTheme }),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to sync theme preference:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant='outline' size='icon'>
|
||||||
|
<Sun className='h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0' />
|
||||||
|
<Moon className='absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100' />
|
||||||
|
<span className='sr-only'>Toggle theme</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align='end'>
|
||||||
|
<DropdownMenuItem onClick={() => handleThemeChange('light')}>Light</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => handleThemeChange('dark')}>Dark</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => handleThemeChange('system')}>System</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,8 +5,10 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { User, Edit, Mail, Calendar, Save, X } from 'lucide-react';
|
import { User, Edit, Mail, Calendar, Save, X, Palette } from 'lucide-react';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
import { ModeToggle } from '@/components/ui/mode-toggle';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -15,6 +17,7 @@ interface User {
|
|||||||
surname: string;
|
surname: string;
|
||||||
role: string;
|
role: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
themePreference: 'light' | 'dark' | 'system';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProfileFormData {
|
interface ProfileFormData {
|
||||||
@@ -32,6 +35,7 @@ export function UserProfile() {
|
|||||||
surname: '',
|
surname: '',
|
||||||
});
|
});
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
const updateFormData = (field: keyof ProfileFormData, value: string) => {
|
const updateFormData = (field: keyof ProfileFormData, value: string) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
@@ -54,6 +58,10 @@ export function UserProfile() {
|
|||||||
name: userData.user.name,
|
name: userData.user.name,
|
||||||
surname: userData.user.surname,
|
surname: userData.user.surname,
|
||||||
});
|
});
|
||||||
|
// Sync theme with user preference if available
|
||||||
|
if (userData.user.themePreference && userData.user.themePreference !== theme) {
|
||||||
|
setTheme(userData.user.themePreference);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
@@ -139,8 +147,8 @@ export function UserProfile() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent className='p-6'>
|
<CardContent className='p-6'>
|
||||||
<div className='flex items-center justify-center'>
|
<div className='flex items-center justify-center'>
|
||||||
<div className='animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900'></div>
|
<div className='animate-spin rounded-full h-8 w-8 border-b-2 border-primary'></div>
|
||||||
<p className='ml-2'>Loading profile...</p>
|
<p className='ml-2 text-muted-foreground'>Loading profile...</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -151,7 +159,7 @@ export function UserProfile() {
|
|||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className='p-6'>
|
<CardContent className='p-6'>
|
||||||
<div className='text-center text-gray-500'>
|
<div className='text-center text-muted-foreground'>
|
||||||
<p>Unable to load user profile</p>
|
<p>Unable to load user profile</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -182,7 +190,7 @@ export function UserProfile() {
|
|||||||
placeholder='Enter your first name'
|
placeholder='Enter your first name'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded'>
|
<div className='flex items-center gap-2 p-2 bg-muted rounded'>
|
||||||
<span>{user.name}</span>
|
<span>{user.name}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -199,7 +207,7 @@ export function UserProfile() {
|
|||||||
placeholder='Enter your last name'
|
placeholder='Enter your last name'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded'>
|
<div className='flex items-center gap-2 p-2 bg-muted rounded'>
|
||||||
<span>{user.surname}</span>
|
<span>{user.surname}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -208,20 +216,20 @@ export function UserProfile() {
|
|||||||
{/* Email (Read-only) */}
|
{/* Email (Read-only) */}
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
<Label htmlFor='email'>Email Address</Label>
|
<Label htmlFor='email'>Email Address</Label>
|
||||||
<div className='flex items-center gap-2 p-2 bg-gray-100 rounded text-gray-600'>
|
<div className='flex items-center gap-2 p-2 bg-muted rounded text-muted-foreground'>
|
||||||
<Mail className='h-4 w-4' />
|
<Mail className='h-4 w-4' />
|
||||||
<span>{user.email}</span>
|
<span>{user.email}</span>
|
||||||
<span className='text-xs text-gray-500 ml-auto'>(Read-only)</span>
|
<span className='text-xs text-muted-foreground/60 ml-auto'>(Read-only)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Member Since */}
|
{/* Member Since */}
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
<Label>Member Since</Label>
|
<Label>Member Since</Label>
|
||||||
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded'>
|
<div className='flex items-center gap-2 p-2 bg-muted rounded'>
|
||||||
<Calendar className='h-4 w-4' />
|
<Calendar className='h-4 w-4' />
|
||||||
<span>
|
<span>
|
||||||
{new Date(user.createdAt).toLocaleDateString('en-US', {
|
{new Date(user.createdAt).toLocaleDateString('en-IE', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
@@ -268,13 +276,49 @@ export function UserProfile() {
|
|||||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-4'>
|
<div className='grid grid-cols-1 md:grid-cols-2 gap-4'>
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
<Label>Account Type</Label>
|
<Label>Account Type</Label>
|
||||||
<div className='p-2 bg-blue-50 rounded text-blue-800 capitalize font-medium'>
|
<div className='p-2 bg-blue-50 dark:bg-blue-900/20 rounded text-blue-800 dark:text-blue-200 capitalize font-medium'>
|
||||||
{user.role}
|
{user.role}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
<Label>User ID</Label>
|
<Label>User ID</Label>
|
||||||
<div className='p-2 bg-gray-50 rounded text-gray-600 font-mono text-sm'>{user.id}</div>
|
<div className='p-2 bg-muted rounded text-muted-foreground font-mono text-sm'>
|
||||||
|
{user.id}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Theme Preferences Card */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className='flex items-center gap-2'>
|
||||||
|
<Palette className='h-5 w-5' />
|
||||||
|
Theme Preferences
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className='space-y-4'>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<Label>App Theme</Label>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Choose how the app appears to you. System will use your device's theme setting.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ModeToggle />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='p-3 bg-muted/50 rounded-lg'>
|
||||||
|
<p className='text-sm'>
|
||||||
|
<strong>Current theme:</strong>{' '}
|
||||||
|
<span className='capitalize'>{theme === 'system' ? 'System preference' : theme}</span>
|
||||||
|
</p>
|
||||||
|
<p className='text-xs text-muted-foreground mt-1'>
|
||||||
|
Your theme preference is automatically saved and will be applied across all your
|
||||||
|
sessions.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# 🇮🇪 Irish Localization Implementation Complete
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Successfully implemented Irish localization for the table tennis booking app, with Monday as the first day of the week and Irish (en-IE) date formatting throughout.
|
||||||
|
|
||||||
|
## Key Changes Made
|
||||||
|
|
||||||
|
### 1. Utility Functions (`lib/utils.ts`)
|
||||||
|
|
||||||
|
- **Updated `getWeekDays()`**: Now returns Monday-Sunday order with proper JS day values
|
||||||
|
- **Added Irish conversion functions**:
|
||||||
|
- `getIrishDayOfWeek()`: Converts JS getDay() (0=Sunday) to Irish standard (0=Monday)
|
||||||
|
- `getJavaScriptDayOfWeek()`: Converts Irish day index back to JS getDay() format
|
||||||
|
- `getIrishDayName()`: Gets day name using Irish week start
|
||||||
|
- **Updated `formatDate()`**: Changed from 'en-US' to 'en-IE' locale
|
||||||
|
|
||||||
|
### 2. Admin Time Slot Management (`components/admin/AdminTimeSlotManagement.tsx`)
|
||||||
|
|
||||||
|
- **Irish week order**: Days now display Monday through Sunday
|
||||||
|
- **Updated constants**: `IRISH_DAY_ORDER = [1, 2, 3, 4, 5, 6, 0]` for correct mapping
|
||||||
|
- **Form defaults**: New time slots default to Monday (dayOfWeek: 1)
|
||||||
|
- **Display logic**: Correctly maps JS day values to Irish display order
|
||||||
|
- **Dropdown options**: Time slot creation shows days in Irish order
|
||||||
|
|
||||||
|
### 3. Calendar UI Component (`components/ui/calendar.tsx`)
|
||||||
|
|
||||||
|
- **Added `weekStartsOn={1}`**: Calendar widget now starts with Monday
|
||||||
|
- **Updated locale**: Changed month formatting to 'en-IE'
|
||||||
|
- **Data attributes**: Updated to use Irish locale for consistency
|
||||||
|
|
||||||
|
### 4. Enhanced Booking Calendar (`components/booking/enhanced-booking-calendar.tsx`)
|
||||||
|
|
||||||
|
- **Date formatting**: All `toLocaleDateString()` calls updated to 'en-IE'
|
||||||
|
- **Consistent display**: Weekday abbreviations and full date formats use Irish locale
|
||||||
|
- **Time slot logic**: Maintains compatibility with JS day values in database
|
||||||
|
|
||||||
|
### 5. All Other Components
|
||||||
|
|
||||||
|
Updated locale formatting in:
|
||||||
|
|
||||||
|
- `components/dashboard/BookingCalendar.tsx`
|
||||||
|
- `components/user/user-profile.tsx`
|
||||||
|
- `components/notifications/announcements.tsx`
|
||||||
|
- `components/admin/AdminUserManagement.tsx`
|
||||||
|
- `components/admin/AdminAnnouncementManagement.tsx`
|
||||||
|
- `components/admin/AdminCourtManagement.tsx`
|
||||||
|
|
||||||
|
### 6. API Compatibility Fix
|
||||||
|
|
||||||
|
- **Fixed Next.js 15 async params**: Updated time-slots/[id]/route.ts to properly handle async params
|
||||||
|
|
||||||
|
## Database Compatibility
|
||||||
|
|
||||||
|
- **No database changes needed**: Database still stores JavaScript's getDay() values (0=Sunday, 6=Saturday)
|
||||||
|
- **Mapping handled in UI**: All conversion between JS day values and Irish display order handled in frontend
|
||||||
|
- **Backward compatibility**: Existing time slots and bookings work seamlessly
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
### Day Mapping Logic
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// JavaScript getDay() values remain in database:
|
||||||
|
// 0=Sunday, 1=Monday, 2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, 6=Saturday
|
||||||
|
|
||||||
|
// Irish display order (Monday first):
|
||||||
|
const IRISH_DAY_ORDER = [1, 2, 3, 4, 5, 6, 0]; // Maps to Monday-Sunday
|
||||||
|
|
||||||
|
// Admin panel shows: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
|
||||||
|
// But stores as: 1, 2, 3, 4, 5, 6, 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Locale Settings
|
||||||
|
|
||||||
|
- **Calendar**: Starts with Monday (`weekStartsOn={1}`)
|
||||||
|
- **Date Format**: Irish format (DD/MM/YYYY pattern via en-IE)
|
||||||
|
- **Time Format**: Maintained 24-hour format as requested
|
||||||
|
- **Day Names**: All components use Irish week order for display
|
||||||
|
|
||||||
|
## User Impact
|
||||||
|
|
||||||
|
1. **Admin Interface**: Time slot management shows Monday first, making it intuitive for Irish users
|
||||||
|
2. **Booking Calendar**: Date selection and display follow Irish conventions
|
||||||
|
3. **Date Display**: All dates throughout the app use Irish formatting (en-IE)
|
||||||
|
4. **Week View**: Calendar widgets start with Monday as expected in Ireland
|
||||||
|
|
||||||
|
## Testing Verification
|
||||||
|
|
||||||
|
- ✅ Created test script (`scripts/test-irish-localization.js`) to verify settings
|
||||||
|
- ✅ Confirmed day mapping logic works correctly
|
||||||
|
- ✅ Verified Irish date formatting across all components
|
||||||
|
- ✅ Time slot management displays in correct order
|
||||||
|
- ✅ Calendar widgets start with Monday
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- **Cultural Alignment**: Follows Irish/European convention of Monday as week start
|
||||||
|
- **User Experience**: More intuitive for Irish users
|
||||||
|
- **Consistency**: All date formatting uses Irish locale
|
||||||
|
- **Maintainable**: Clean separation between database storage and display logic
|
||||||
|
- **No Breaking Changes**: Existing bookings and time slots continue to work
|
||||||
|
|
||||||
|
## Future Considerations
|
||||||
|
|
||||||
|
- Could extend to full internationalization (i18n) if needed for other locales
|
||||||
|
- Day conversion utilities are ready for reuse across the application
|
||||||
|
- Database schema remains flexible for any future locale changes
|
||||||
@@ -12,6 +12,9 @@ export const users = sqliteTable('users', {
|
|||||||
role: text('role', { enum: ['user', 'admin'] })
|
role: text('role', { enum: ['user', 'admin'] })
|
||||||
.notNull()
|
.notNull()
|
||||||
.default('user'),
|
.default('user'),
|
||||||
|
themePreference: text('theme_preference', { enum: ['light', 'dark', 'system'] })
|
||||||
|
.notNull()
|
||||||
|
.default('system'),
|
||||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
||||||
});
|
});
|
||||||
|
|||||||
+20
-2
@@ -18,7 +18,7 @@ export function formatTime(time: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatDate(date: string): string {
|
export function formatDate(date: string): string {
|
||||||
return new Date(date).toLocaleDateString('en-US', {
|
return new Date(date).toLocaleDateString('en-IE', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
@@ -40,18 +40,36 @@ export function isWithinBookingWindow(date: string): boolean {
|
|||||||
return bookingDate >= today && bookingDate <= maxDate;
|
return bookingDate >= today && bookingDate <= maxDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ireland localization - Monday as first day of week
|
||||||
export function getWeekDays(): Array<{ value: number; label: string }> {
|
export function getWeekDays(): Array<{ value: number; label: string }> {
|
||||||
return [
|
return [
|
||||||
{ value: 0, label: 'Sunday' },
|
|
||||||
{ value: 1, label: 'Monday' },
|
{ value: 1, label: 'Monday' },
|
||||||
{ value: 2, label: 'Tuesday' },
|
{ value: 2, label: 'Tuesday' },
|
||||||
{ value: 3, label: 'Wednesday' },
|
{ value: 3, label: 'Wednesday' },
|
||||||
{ value: 4, label: 'Thursday' },
|
{ value: 4, label: 'Thursday' },
|
||||||
{ value: 5, label: 'Friday' },
|
{ value: 5, label: 'Friday' },
|
||||||
{ value: 6, label: 'Saturday' },
|
{ value: 6, label: 'Saturday' },
|
||||||
|
{ value: 0, label: 'Sunday' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert JavaScript's getDay() (0=Sunday) to Irish standard (0=Monday)
|
||||||
|
export function getIrishDayOfWeek(date: Date): number {
|
||||||
|
const jsDay = date.getDay();
|
||||||
|
return jsDay === 0 ? 6 : jsDay - 1; // Sunday becomes 6, Monday becomes 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Irish day index (0=Monday) back to JavaScript's getDay() format
|
||||||
|
export function getJavaScriptDayOfWeek(irishDay: number): number {
|
||||||
|
return irishDay === 6 ? 0 : irishDay + 1; // 6 becomes Sunday (0), others shift up
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get day name using Irish week start (0=Monday)
|
||||||
|
export function getIrishDayName(irishDayIndex: number): string {
|
||||||
|
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||||
|
return days[irishDayIndex];
|
||||||
|
}
|
||||||
|
|
||||||
export function generateTimeSlots(startHour: number, endHour: number): string[] {
|
export function generateTimeSlots(startHour: number, endHour: number): string[] {
|
||||||
const slots = [];
|
const slots = [];
|
||||||
for (let hour = startHour; hour < endHour; hour++) {
|
for (let hour = startHour; hour < endHour; hour++) {
|
||||||
|
|||||||
Generated
+8
-117
@@ -14,7 +14,7 @@
|
|||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-avatar": "^1.0.4",
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-icons": "^1.3.2",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
@@ -70,7 +70,6 @@
|
|||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -2329,7 +2328,6 @@
|
|||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"string-width": "^5.1.2",
|
"string-width": "^5.1.2",
|
||||||
@@ -2347,7 +2345,6 @@
|
|||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -2360,7 +2357,6 @@
|
|||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^6.0.1"
|
"ansi-regex": "^6.0.1"
|
||||||
@@ -2376,7 +2372,6 @@
|
|||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
@@ -2387,7 +2382,6 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -2397,14 +2391,12 @@
|
|||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.31",
|
"version": "0.3.31",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
@@ -2602,7 +2594,6 @@
|
|||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.stat": "2.0.5",
|
"@nodelib/fs.stat": "2.0.5",
|
||||||
@@ -2616,7 +2607,6 @@
|
|||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
@@ -2626,7 +2616,6 @@
|
|||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.scandir": "2.1.5",
|
"@nodelib/fs.scandir": "2.1.5",
|
||||||
@@ -2650,7 +2639,6 @@
|
|||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -4396,7 +4384,7 @@
|
|||||||
"version": "7.6.13",
|
"version": "7.6.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
|
||||||
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
|
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
@@ -4431,7 +4419,7 @@
|
|||||||
"version": "20.19.17",
|
"version": "20.19.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz",
|
||||||
"integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==",
|
"integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
@@ -4452,14 +4440,14 @@
|
|||||||
"version": "15.7.15",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.24",
|
"version": "18.3.24",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz",
|
||||||
"integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==",
|
"integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
@@ -4470,7 +4458,7 @@
|
|||||||
"version": "18.3.7",
|
"version": "18.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^18.0.0"
|
"@types/react": "^18.0.0"
|
||||||
@@ -5064,7 +5052,6 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -5074,7 +5061,6 @@
|
|||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
@@ -5090,14 +5076,12 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/anymatch": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
@@ -5111,7 +5095,6 @@
|
|||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
@@ -5398,7 +5381,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/base64-js": {
|
"node_modules/base64-js": {
|
||||||
@@ -5452,7 +5434,6 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -5492,7 +5473,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
@@ -5502,7 +5482,6 @@
|
|||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.1.1"
|
"fill-range": "^7.1.1"
|
||||||
@@ -5659,7 +5638,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -5702,7 +5680,6 @@
|
|||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": "~3.1.2",
|
"anymatch": "~3.1.2",
|
||||||
@@ -5727,7 +5704,6 @@
|
|||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
@@ -5790,7 +5766,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
@@ -5803,7 +5778,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
@@ -5843,7 +5817,6 @@
|
|||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"path-key": "^3.1.0",
|
"path-key": "^3.1.0",
|
||||||
@@ -5858,7 +5831,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"cssesc": "bin/cssesc"
|
"cssesc": "bin/cssesc"
|
||||||
@@ -5871,7 +5843,7 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/d": {
|
"node_modules/d": {
|
||||||
@@ -6075,7 +6047,6 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/difflib": {
|
"node_modules/difflib": {
|
||||||
@@ -6094,7 +6065,6 @@
|
|||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/doctrine": {
|
"node_modules/doctrine": {
|
||||||
@@ -6278,7 +6248,6 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/ecdsa-sig-formatter": {
|
"node_modules/ecdsa-sig-formatter": {
|
||||||
@@ -6301,7 +6270,6 @@
|
|||||||
"version": "9.2.2",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/end-of-stream": {
|
"node_modules/end-of-stream": {
|
||||||
@@ -7252,7 +7220,6 @@
|
|||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||||
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.stat": "^2.0.2",
|
"@nodelib/fs.stat": "^2.0.2",
|
||||||
@@ -7269,7 +7236,6 @@
|
|||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
@@ -7315,7 +7281,6 @@
|
|||||||
"version": "1.19.1",
|
"version": "1.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||||
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
@@ -7344,7 +7309,6 @@
|
|||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
@@ -7412,7 +7376,6 @@
|
|||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||||
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "^7.0.6",
|
"cross-spawn": "^7.0.6",
|
||||||
@@ -7456,7 +7419,6 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -7471,7 +7433,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -7618,7 +7579,6 @@
|
|||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.3"
|
"is-glob": "^4.0.3"
|
||||||
@@ -7789,7 +7749,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -7969,7 +7928,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"binary-extensions": "^2.0.0"
|
"binary-extensions": "^2.0.0"
|
||||||
@@ -8022,7 +7980,6 @@
|
|||||||
"version": "2.16.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
@@ -8073,7 +8030,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -8099,7 +8055,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -8128,7 +8083,6 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
@@ -8167,7 +8121,6 @@
|
|||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
@@ -8376,7 +8329,6 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/iterator.prototype": {
|
"node_modules/iterator.prototype": {
|
||||||
@@ -8401,7 +8353,6 @@
|
|||||||
"version": "3.4.3",
|
"version": "3.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/cliui": "^8.0.2"
|
"@isaacs/cliui": "^8.0.2"
|
||||||
@@ -8417,7 +8368,6 @@
|
|||||||
"version": "1.21.7",
|
"version": "1.21.7",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
@@ -8611,7 +8561,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
@@ -8624,7 +8573,6 @@
|
|||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
@@ -8716,7 +8664,6 @@
|
|||||||
"version": "10.4.3",
|
"version": "10.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/lru-queue": {
|
"node_modules/lru-queue": {
|
||||||
@@ -8772,7 +8719,6 @@
|
|||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
@@ -8782,7 +8728,6 @@
|
|||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.3",
|
"braces": "^3.0.3",
|
||||||
@@ -8833,7 +8778,6 @@
|
|||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
@@ -8855,7 +8799,6 @@
|
|||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"any-promise": "^1.0.0",
|
"any-promise": "^1.0.0",
|
||||||
@@ -9040,7 +8983,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -9060,7 +9002,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -9070,7 +9011,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -9270,7 +9210,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0"
|
"license": "BlueOak-1.0.0"
|
||||||
},
|
},
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
@@ -9310,7 +9249,6 @@
|
|||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -9320,14 +9258,12 @@
|
|||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry": {
|
"node_modules/path-scurry": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
@@ -9350,7 +9286,6 @@
|
|||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
@@ -9363,7 +9298,6 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -9373,7 +9307,6 @@
|
|||||||
"version": "4.0.7",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||||
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -9393,7 +9326,6 @@
|
|||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -9422,7 +9354,6 @@
|
|||||||
"version": "15.1.0",
|
"version": "15.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
||||||
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"postcss-value-parser": "^4.0.0",
|
"postcss-value-parser": "^4.0.0",
|
||||||
@@ -9440,7 +9371,6 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
|
||||||
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
|
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -9466,7 +9396,6 @@
|
|||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -9492,7 +9421,6 @@
|
|||||||
"version": "6.1.2",
|
"version": "6.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssesc": "^3.0.0",
|
"cssesc": "^3.0.0",
|
||||||
@@ -9506,7 +9434,6 @@
|
|||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/prebuild-install": {
|
"node_modules/prebuild-install": {
|
||||||
@@ -9581,7 +9508,6 @@
|
|||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -9770,7 +9696,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pify": "^2.3.0"
|
"pify": "^2.3.0"
|
||||||
@@ -9794,7 +9719,6 @@
|
|||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
@@ -9851,7 +9775,6 @@
|
|||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.16.0",
|
"is-core-module": "^2.16.0",
|
||||||
@@ -9892,7 +9815,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"iojs": ">=1.0.0",
|
"iojs": ">=1.0.0",
|
||||||
@@ -9966,7 +9888,6 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -10175,7 +10096,6 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shebang-regex": "^3.0.0"
|
"shebang-regex": "^3.0.0"
|
||||||
@@ -10188,7 +10108,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -10274,7 +10193,6 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
@@ -10399,7 +10317,6 @@
|
|||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eastasianwidth": "^0.2.0",
|
"eastasianwidth": "^0.2.0",
|
||||||
@@ -10418,7 +10335,6 @@
|
|||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"emoji-regex": "^8.0.0",
|
"emoji-regex": "^8.0.0",
|
||||||
@@ -10433,14 +10349,12 @@
|
|||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/string-width/node_modules/ansi-regex": {
|
"node_modules/string-width/node_modules/ansi-regex": {
|
||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -10453,7 +10367,6 @@
|
|||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^6.0.1"
|
"ansi-regex": "^6.0.1"
|
||||||
@@ -10582,7 +10495,6 @@
|
|||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1"
|
"ansi-regex": "^5.0.1"
|
||||||
@@ -10596,7 +10508,6 @@
|
|||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1"
|
"ansi-regex": "^5.0.1"
|
||||||
@@ -10668,7 +10579,6 @@
|
|||||||
"version": "3.35.0",
|
"version": "3.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||||
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
|
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.2",
|
"@jridgewell/gen-mapping": "^0.3.2",
|
||||||
@@ -10691,7 +10601,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -10701,7 +10610,6 @@
|
|||||||
"version": "10.4.5",
|
"version": "10.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"foreground-child": "^3.1.0",
|
"foreground-child": "^3.1.0",
|
||||||
@@ -10722,7 +10630,6 @@
|
|||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"brace-expansion": "^2.0.1"
|
||||||
@@ -10764,7 +10671,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -10787,7 +10693,6 @@
|
|||||||
"version": "3.4.17",
|
"version": "3.4.17",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||||
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
@@ -10834,7 +10739,6 @@
|
|||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
||||||
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -10905,7 +10809,6 @@
|
|||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||||
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"any-promise": "^1.0.0"
|
"any-promise": "^1.0.0"
|
||||||
@@ -10915,7 +10818,6 @@
|
|||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"thenify": ">= 3.1.0 < 4"
|
"thenify": ">= 3.1.0 < 4"
|
||||||
@@ -10990,7 +10892,6 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
@@ -11016,7 +10917,6 @@
|
|||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/tsconfig-paths": {
|
"node_modules/tsconfig-paths": {
|
||||||
@@ -11651,7 +11551,7 @@
|
|||||||
"version": "6.21.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unrs-resolver": {
|
"node_modules/unrs-resolver": {
|
||||||
@@ -11806,7 +11706,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
@@ -11928,7 +11827,6 @@
|
|||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^6.1.0",
|
"ansi-styles": "^6.1.0",
|
||||||
@@ -11947,7 +11845,6 @@
|
|||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
@@ -11965,14 +11862,12 @@
|
|||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
|
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"emoji-regex": "^8.0.0",
|
"emoji-regex": "^8.0.0",
|
||||||
@@ -11987,7 +11882,6 @@
|
|||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -12000,7 +11894,6 @@
|
|||||||
"version": "6.2.3",
|
"version": "6.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -12013,7 +11906,6 @@
|
|||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^6.0.1"
|
"ansi-regex": "^6.0.1"
|
||||||
@@ -12035,7 +11927,6 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
||||||
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
|
|||||||
+5
-2
@@ -10,7 +10,10 @@
|
|||||||
"db:push": "drizzle-kit push:sqlite",
|
"db:push": "drizzle-kit push:sqlite",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:studio": "drizzle-kit studio",
|
"db:studio": "drizzle-kit studio",
|
||||||
"db:setup": "tsx scripts/setup-db.ts",
|
"db:setup": "tsx scripts/setup-database.ts",
|
||||||
|
"db:reset": "tsx scripts/reset-db.ts",
|
||||||
|
"db:reset-confirm": "tsx scripts/reset-db.ts --confirm",
|
||||||
|
"db:seed": "tsx scripts/setup-database.ts --essential-only",
|
||||||
"postinstall": "npm run db:push"
|
"postinstall": "npm run db:push"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -19,7 +22,7 @@
|
|||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-avatar": "^1.0.4",
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-icons": "^1.3.2",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
|
|||||||
Executable
+101
@@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Cleanup old seed scripts and organize database utilities
|
||||||
|
# This script consolidates old individual seed scripts into the new unified system
|
||||||
|
|
||||||
|
echo "🧹 Cleaning up old database scripts..."
|
||||||
|
|
||||||
|
# Create a backup directory for old scripts
|
||||||
|
mkdir -p scripts/old-seeds
|
||||||
|
|
||||||
|
# Move old individual seed scripts to backup
|
||||||
|
echo "📦 Moving old seed scripts to scripts/old-seeds/..."
|
||||||
|
|
||||||
|
if [ -f "scripts/seed-data.ts" ]; then
|
||||||
|
mv scripts/seed-data.ts scripts/old-seeds/
|
||||||
|
echo " ✓ Moved seed-data.ts"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "scripts/seed-announcements.ts" ]; then
|
||||||
|
mv scripts/seed-announcements.ts scripts/old-seeds/
|
||||||
|
echo " ✓ Moved seed-announcements.ts"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "scripts/seed-time-slots.ts" ]; then
|
||||||
|
mv scripts/seed-time-slots.ts scripts/old-seeds/
|
||||||
|
echo " ✓ Moved seed-time-slots.ts"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "scripts/init-admin-data.ts" ]; then
|
||||||
|
mv scripts/init-admin-data.ts scripts/old-seeds/
|
||||||
|
echo " ✓ Moved init-admin-data.ts"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove old reset script if it exists
|
||||||
|
if [ -f "scripts/reset-database.ts" ]; then
|
||||||
|
mv scripts/reset-database.ts scripts/old-seeds/
|
||||||
|
echo " ✓ Moved old reset-database.ts"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a README in the old-seeds directory
|
||||||
|
cat > scripts/old-seeds/README.md << 'EOF'
|
||||||
|
# Old Seed Scripts (Archived)
|
||||||
|
|
||||||
|
These are the original individual seed scripts that have been consolidated into the new unified database setup system.
|
||||||
|
|
||||||
|
## Consolidated Into:
|
||||||
|
|
||||||
|
- **setup-database.ts** - Unified setup script with all functionality
|
||||||
|
- **reset-db.ts** - Improved reset script with safety features
|
||||||
|
|
||||||
|
## Original Scripts:
|
||||||
|
|
||||||
|
- `seed-data.ts` - Sample bookings and activity logs
|
||||||
|
- `seed-announcements.ts` - Test announcements
|
||||||
|
- `seed-time-slots.ts` - Time slot configuration
|
||||||
|
- `init-admin-data.ts` - Admin dashboard initialization
|
||||||
|
- `reset-database.ts` - Original reset script
|
||||||
|
|
||||||
|
## Migration Notes:
|
||||||
|
|
||||||
|
All functionality from these scripts has been intelligently integrated into the new system:
|
||||||
|
|
||||||
|
### New Command Equivalents:
|
||||||
|
|
||||||
|
| Old Script | New Command |
|
||||||
|
|------------|-------------|
|
||||||
|
| `tsx scripts/seed-data.ts` | `npm run db:setup` |
|
||||||
|
| `tsx scripts/seed-announcements.ts` | Integrated into setup |
|
||||||
|
| `tsx scripts/seed-time-slots.ts` | Integrated into setup |
|
||||||
|
| `tsx scripts/init-admin-data.ts` | Integrated into setup |
|
||||||
|
| `tsx scripts/reset-database.ts` | `npm run db:reset-confirm` |
|
||||||
|
|
||||||
|
### Advantages of New System:
|
||||||
|
|
||||||
|
1. **Intelligent Setup** - Automatically handles dependencies and order
|
||||||
|
2. **Flexible Options** - Essential-only or full sample data modes
|
||||||
|
3. **Safety Features** - Reset confirmation and detailed logging
|
||||||
|
4. **Better Documentation** - Comprehensive help and summaries
|
||||||
|
5. **Single Source** - All database setup in one place
|
||||||
|
|
||||||
|
These old scripts are kept for reference but should not be used in new development.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo " ✓ Created README in old-seeds directory"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Cleanup complete!"
|
||||||
|
echo ""
|
||||||
|
echo "📋 Summary of changes:"
|
||||||
|
echo " • Old seed scripts moved to scripts/old-seeds/"
|
||||||
|
echo " • New unified system active:"
|
||||||
|
echo " - setup-database.ts (comprehensive setup)"
|
||||||
|
echo " - reset-db.ts (safe reset with confirmation)"
|
||||||
|
echo ""
|
||||||
|
echo "🚀 New database commands:"
|
||||||
|
echo " npm run db:setup # Full setup with sample data"
|
||||||
|
echo " npm run db:seed # Essential data only"
|
||||||
|
echo " npm run db:reset # Safe reset (shows warning)"
|
||||||
|
echo " npm run db:reset-confirm # Immediate reset"
|
||||||
|
echo ""
|
||||||
|
echo "📖 See DATABASE_SETUP.md for detailed documentation"
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Old Seed Scripts (Archived)
|
||||||
|
|
||||||
|
These are the original individual seed scripts that have been consolidated into the new unified database setup system.
|
||||||
|
|
||||||
|
## Consolidated Into:
|
||||||
|
|
||||||
|
- **setup-database.ts** - Unified setup script with all functionality
|
||||||
|
- **reset-db.ts** - Improved reset script with safety features
|
||||||
|
|
||||||
|
## Original Scripts:
|
||||||
|
|
||||||
|
- `seed-data.ts` - Sample bookings and activity logs
|
||||||
|
- `seed-announcements.ts` - Test announcements
|
||||||
|
- `seed-time-slots.ts` - Time slot configuration
|
||||||
|
- `init-admin-data.ts` - Admin dashboard initialization
|
||||||
|
- `reset-database.ts` - Original reset script
|
||||||
|
|
||||||
|
## Migration Notes:
|
||||||
|
|
||||||
|
All functionality from these scripts has been intelligently integrated into the new system:
|
||||||
|
|
||||||
|
### New Command Equivalents:
|
||||||
|
|
||||||
|
| Old Script | New Command |
|
||||||
|
|------------|-------------|
|
||||||
|
| `tsx scripts/seed-data.ts` | `npm run db:setup` |
|
||||||
|
| `tsx scripts/seed-announcements.ts` | Integrated into setup |
|
||||||
|
| `tsx scripts/seed-time-slots.ts` | Integrated into setup |
|
||||||
|
| `tsx scripts/init-admin-data.ts` | Integrated into setup |
|
||||||
|
| `tsx scripts/reset-database.ts` | `npm run db:reset-confirm` |
|
||||||
|
|
||||||
|
### Advantages of New System:
|
||||||
|
|
||||||
|
1. **Intelligent Setup** - Automatically handles dependencies and order
|
||||||
|
2. **Flexible Options** - Essential-only or full sample data modes
|
||||||
|
3. **Safety Features** - Reset confirmation and detailed logging
|
||||||
|
4. **Better Documentation** - Comprehensive help and summaries
|
||||||
|
5. **Single Source** - All database setup in one place
|
||||||
|
|
||||||
|
These old scripts are kept for reference but should not be used in new development.
|
||||||
@@ -31,6 +31,18 @@ async function initializeAdminData() {
|
|||||||
value: '1',
|
value: '1',
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
key: 'allow_booking_modifications',
|
||||||
|
value: 'true',
|
||||||
|
updatedAt: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
key: 'booking_modification_hours_before',
|
||||||
|
value: '2',
|
||||||
|
updatedAt: now,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Insert settings if they don't exist
|
// Insert settings if they don't exist
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import Database from 'better-sqlite3';
|
||||||
|
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||||
|
import * as schema from '../lib/db/schema';
|
||||||
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
|
const sqlite = new Database('./sqlite.db');
|
||||||
|
const db = drizzle(sqlite, { schema });
|
||||||
|
|
||||||
|
interface ResetOptions {
|
||||||
|
confirm?: boolean;
|
||||||
|
keepData?: boolean;
|
||||||
|
verbose?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetDatabase(options: ResetOptions = {}) {
|
||||||
|
const { confirm = false, keepData = false, verbose = false } = options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!confirm) {
|
||||||
|
console.log(`
|
||||||
|
⚠️ WARNING: This will permanently delete ALL data in the database!
|
||||||
|
|
||||||
|
To confirm the reset, run:
|
||||||
|
tsx scripts/reset-db.ts --confirm
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--confirm Confirm the destructive operation
|
||||||
|
--keep-data Only reset schema, keep existing data if possible
|
||||||
|
--verbose Show detailed output
|
||||||
|
--help Show this help message
|
||||||
|
|
||||||
|
Current database: sqlite.db
|
||||||
|
`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🗑️ Resetting database...\n');
|
||||||
|
|
||||||
|
if (keepData) {
|
||||||
|
console.log('🔄 Schema reset mode - attempting to preserve data...');
|
||||||
|
} else {
|
||||||
|
console.log('💥 Full reset mode - all data will be lost!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all tables to drop
|
||||||
|
const tables = [
|
||||||
|
'activity_logs',
|
||||||
|
'metrics',
|
||||||
|
'bookings',
|
||||||
|
'announcements',
|
||||||
|
'time_slots',
|
||||||
|
'settings',
|
||||||
|
'courts',
|
||||||
|
'users',
|
||||||
|
'__drizzle_migrations',
|
||||||
|
'__old_push_courts',
|
||||||
|
'__old_push_users',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Drop all tables
|
||||||
|
let droppedCount = 0;
|
||||||
|
for (const table of tables) {
|
||||||
|
try {
|
||||||
|
await db.run(sql.raw(`DROP TABLE IF EXISTS ${table}`));
|
||||||
|
droppedCount++;
|
||||||
|
if (verbose) console.log(` ✓ Dropped table: ${table}`);
|
||||||
|
} catch (error) {
|
||||||
|
if (verbose) console.log(` - Table ${table} doesn't exist or error dropping`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n✅ Database reset complete! Dropped ${droppedCount} tables.`);
|
||||||
|
|
||||||
|
if (!keepData) {
|
||||||
|
console.log('\n📝 To set up the database with fresh data, run:');
|
||||||
|
console.log(' tsx scripts/setup-database.ts');
|
||||||
|
console.log(' or');
|
||||||
|
console.log(' npm run db:setup');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n💡 Database file location: ./sqlite.db');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Database reset failed:', error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
sqlite.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get database statistics before reset
|
||||||
|
async function getDatabaseStats() {
|
||||||
|
try {
|
||||||
|
const stats: Record<string, number> = {};
|
||||||
|
|
||||||
|
const tableQueries = [
|
||||||
|
{ name: 'users', table: schema.users },
|
||||||
|
{ name: 'courts', table: schema.courts },
|
||||||
|
{ name: 'bookings', table: schema.bookings },
|
||||||
|
{ name: 'announcements', table: schema.announcements },
|
||||||
|
{ name: 'timeSlots', table: schema.timeSlots },
|
||||||
|
{ name: 'settings', table: schema.settings },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { name, table } of tableQueries) {
|
||||||
|
try {
|
||||||
|
const count = await db.select().from(table);
|
||||||
|
stats[name] = count.length;
|
||||||
|
} catch {
|
||||||
|
stats[name] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
function parseArgs() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const options: ResetOptions = {};
|
||||||
|
|
||||||
|
if (args.includes('--confirm')) {
|
||||||
|
options.confirm = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.includes('--keep-data')) {
|
||||||
|
options.keepData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.includes('--verbose') || args.includes('-v')) {
|
||||||
|
options.verbose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.includes('--help') || args.includes('-h')) {
|
||||||
|
console.log(`
|
||||||
|
Table Tennis Booking System - Database Reset
|
||||||
|
|
||||||
|
Usage: tsx scripts/reset-db.ts [options]
|
||||||
|
|
||||||
|
⚠️ WARNING: This is a destructive operation!
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--confirm Confirm the destructive operation (required)
|
||||||
|
--keep-data Preserve data where possible during schema reset
|
||||||
|
--verbose, -v Show detailed output
|
||||||
|
--help, -h Show this help message
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
tsx scripts/reset-db.ts --confirm # Full reset
|
||||||
|
tsx scripts/reset-db.ts --confirm --verbose # Full reset with details
|
||||||
|
tsx scripts/reset-db.ts --confirm --keep-data # Schema reset only
|
||||||
|
|
||||||
|
After reset, set up the database:
|
||||||
|
tsx scripts/setup-database.ts
|
||||||
|
`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main execution
|
||||||
|
if (require.main === module) {
|
||||||
|
const options = parseArgs();
|
||||||
|
|
||||||
|
// Show current database stats if verbose and confirmed
|
||||||
|
if (options.confirm && options.verbose) {
|
||||||
|
getDatabaseStats().then((stats) => {
|
||||||
|
console.log('📊 Current Database Contents:');
|
||||||
|
Object.entries(stats).forEach(([table, count]) => {
|
||||||
|
console.log(` ${table}: ${count} records`);
|
||||||
|
});
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
resetDatabase(options)
|
||||||
|
.then(() => {
|
||||||
|
console.log('🎯 Database reset completed successfully!');
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('💥 Database reset failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resetDatabase(options)
|
||||||
|
.then(() => {
|
||||||
|
if (options.confirm) {
|
||||||
|
console.log('🎯 Database reset completed successfully!');
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('💥 Database reset failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { resetDatabase };
|
||||||
@@ -0,0 +1,662 @@
|
|||||||
|
import Database from 'better-sqlite3';
|
||||||
|
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||||
|
import * as schema from '../lib/db/schema';
|
||||||
|
import { sql, eq } from 'drizzle-orm';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
import bcrypt from 'bcryptjs';
|
||||||
|
|
||||||
|
const sqlite = new Database('./sqlite.db');
|
||||||
|
const db = drizzle(sqlite, { schema });
|
||||||
|
|
||||||
|
interface SetupOptions {
|
||||||
|
reset?: boolean;
|
||||||
|
seedData?: boolean;
|
||||||
|
verbose?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupDatabase(options: SetupOptions = {}) {
|
||||||
|
const { reset = false, seedData = true, verbose = false } = options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🚀 Starting database setup...\n');
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
await resetTables(verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
await createTables(verbose);
|
||||||
|
await seedBasicData(verbose);
|
||||||
|
|
||||||
|
if (seedData) {
|
||||||
|
await seedSampleData(verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Database setup completed successfully!\n');
|
||||||
|
|
||||||
|
// Print summary
|
||||||
|
await printDatabaseSummary();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Database setup failed:', error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
sqlite.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetTables(verbose: boolean) {
|
||||||
|
if (verbose) console.log('🗑️ Resetting database tables...');
|
||||||
|
|
||||||
|
const tables = [
|
||||||
|
'activity_logs',
|
||||||
|
'metrics',
|
||||||
|
'bookings',
|
||||||
|
'announcements',
|
||||||
|
'time_slots',
|
||||||
|
'settings',
|
||||||
|
'courts',
|
||||||
|
'users',
|
||||||
|
'__drizzle_migrations',
|
||||||
|
'__old_push_courts',
|
||||||
|
'__old_push_users',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const table of tables) {
|
||||||
|
try {
|
||||||
|
await db.run(sql.raw(`DROP TABLE IF EXISTS ${table}`));
|
||||||
|
if (verbose) console.log(` ✓ Dropped table: ${table}`);
|
||||||
|
} catch (error) {
|
||||||
|
if (verbose) console.log(` - Table ${table} doesn't exist or error dropping`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Tables reset complete\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTables(verbose: boolean) {
|
||||||
|
if (verbose) console.log('🏗️ Creating database tables...');
|
||||||
|
|
||||||
|
// Users table
|
||||||
|
await db.run(sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
email TEXT NOT NULL UNIQUE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
surname TEXT NOT NULL,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin')),
|
||||||
|
theme_preference TEXT DEFAULT 'system' CHECK (theme_preference IN ('light', 'dark', 'system')),
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Courts table
|
||||||
|
await db.run(sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS courts (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
is_active INTEGER NOT NULL DEFAULT 1,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Settings table
|
||||||
|
await db.run(sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
key TEXT NOT NULL UNIQUE,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Time slots table
|
||||||
|
await db.run(sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS time_slots (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
day_of_week INTEGER NOT NULL,
|
||||||
|
start_time TEXT NOT NULL,
|
||||||
|
end_time TEXT NOT NULL,
|
||||||
|
is_active INTEGER NOT NULL DEFAULT 1,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Bookings table
|
||||||
|
await db.run(sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS bookings (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
court_id TEXT NOT NULL REFERENCES courts(id) ON DELETE CASCADE,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
start_time TEXT NOT NULL,
|
||||||
|
end_time TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'cancelled')),
|
||||||
|
notes TEXT,
|
||||||
|
partner_name TEXT,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Announcements table
|
||||||
|
await db.run(sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS announcements (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
is_active INTEGER NOT NULL DEFAULT 1,
|
||||||
|
priority TEXT NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high')),
|
||||||
|
expires_at INTEGER,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Activity logs table
|
||||||
|
await db.run(sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS activity_logs (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
action TEXT NOT NULL,
|
||||||
|
entity_type TEXT NOT NULL,
|
||||||
|
entity_id TEXT,
|
||||||
|
details TEXT,
|
||||||
|
ip_address TEXT,
|
||||||
|
user_agent TEXT,
|
||||||
|
created_at INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Metrics table
|
||||||
|
await db.run(sql`
|
||||||
|
CREATE TABLE IF NOT EXISTS metrics (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
metric_type TEXT NOT NULL,
|
||||||
|
period TEXT NOT NULL,
|
||||||
|
value INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (verbose) console.log(' ✓ All tables created successfully');
|
||||||
|
console.log('✅ Database schema ready\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedBasicData(verbose: boolean) {
|
||||||
|
console.log('🌱 Seeding essential data...');
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Check if users already exist
|
||||||
|
const existingUsers = await db.select().from(schema.users);
|
||||||
|
|
||||||
|
if (existingUsers.length === 0) {
|
||||||
|
// Create admin user
|
||||||
|
const adminPassword = await bcrypt.hash('admin123', 12);
|
||||||
|
const adminId = randomUUID();
|
||||||
|
|
||||||
|
await db.insert(schema.users).values({
|
||||||
|
id: adminId,
|
||||||
|
email: 'admin@tabletennis.com',
|
||||||
|
name: 'Admin',
|
||||||
|
surname: 'User',
|
||||||
|
password: adminPassword,
|
||||||
|
role: 'admin',
|
||||||
|
themePreference: 'system',
|
||||||
|
createdAt: new Date(now),
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create test user
|
||||||
|
const userPassword = await bcrypt.hash('user123', 12);
|
||||||
|
const userId = randomUUID();
|
||||||
|
|
||||||
|
await db.insert(schema.users).values({
|
||||||
|
id: userId,
|
||||||
|
email: 'user@tabletennis.com',
|
||||||
|
name: 'Test',
|
||||||
|
surname: 'User',
|
||||||
|
password: userPassword,
|
||||||
|
role: 'user',
|
||||||
|
themePreference: 'system',
|
||||||
|
createdAt: new Date(now),
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (verbose) console.log(' ✓ Created admin and test users');
|
||||||
|
} else {
|
||||||
|
if (verbose) console.log(' - Users already exist, skipping user creation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if courts already exist
|
||||||
|
const existingCourts = await db.select().from(schema.courts);
|
||||||
|
|
||||||
|
if (existingCourts.length === 0) {
|
||||||
|
// Create courts
|
||||||
|
const courtIds = [randomUUID(), randomUUID(), randomUUID()];
|
||||||
|
await db.insert(schema.courts).values([
|
||||||
|
{
|
||||||
|
id: courtIds[0],
|
||||||
|
name: 'Court 1',
|
||||||
|
isActive: true,
|
||||||
|
createdAt: new Date(now),
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: courtIds[1],
|
||||||
|
name: 'Court 2',
|
||||||
|
isActive: true,
|
||||||
|
createdAt: new Date(now),
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: courtIds[2],
|
||||||
|
name: 'Court 3',
|
||||||
|
isActive: true,
|
||||||
|
createdAt: new Date(now),
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (verbose) console.log(' ✓ Created 3 courts');
|
||||||
|
} else {
|
||||||
|
if (verbose) console.log(' - Courts already exist, skipping court creation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert system settings
|
||||||
|
const defaultSettings = [
|
||||||
|
{ key: 'booking_window_days', value: '14' },
|
||||||
|
{ key: 'max_booking_duration_hours', value: '2' },
|
||||||
|
{ key: 'max_bookings_per_user_per_hour_per_day', value: '1' },
|
||||||
|
{ key: 'allow_booking_modifications', value: 'true' },
|
||||||
|
{ key: 'booking_modification_hours_before', value: '2' },
|
||||||
|
{ key: 'min_booking_duration_minutes', value: '60' },
|
||||||
|
{ key: 'booking_start_time', value: '08:00' },
|
||||||
|
{ key: 'booking_end_time', value: '22:00' },
|
||||||
|
{ key: 'allow_weekend_bookings', value: 'true' },
|
||||||
|
{ key: 'facility_name', value: 'Table Tennis Club' },
|
||||||
|
{ key: 'facility_email', value: 'info@tabletennis.com' },
|
||||||
|
{ key: 'facility_phone', value: '+353-1-234-5678' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const setting of defaultSettings) {
|
||||||
|
const existingSetting = await db
|
||||||
|
.select()
|
||||||
|
.from(schema.settings)
|
||||||
|
.where(eq(schema.settings.key, setting.key))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (existingSetting.length === 0) {
|
||||||
|
await db.insert(schema.settings).values({
|
||||||
|
id: randomUUID(),
|
||||||
|
key: setting.key,
|
||||||
|
value: setting.value,
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
});
|
||||||
|
if (verbose) console.log(` ✓ Setting: ${setting.key} = ${setting.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if time slots already exist
|
||||||
|
const existingTimeSlots = await db.select().from(schema.timeSlots);
|
||||||
|
|
||||||
|
if (existingTimeSlots.length === 0) {
|
||||||
|
// Create time slots - Operating hours for each day
|
||||||
|
const timeSlotData = [
|
||||||
|
// Sunday: 12:00 - 17:00 (shorter hours)
|
||||||
|
{ dayOfWeek: 0, startTime: '12:00', endTime: '17:00' },
|
||||||
|
|
||||||
|
// Monday to Thursday: 18:00 - 23:00 (evening sessions)
|
||||||
|
{ dayOfWeek: 1, startTime: '18:00', endTime: '23:00' },
|
||||||
|
{ dayOfWeek: 2, startTime: '18:00', endTime: '23:00' },
|
||||||
|
{ dayOfWeek: 3, startTime: '18:00', endTime: '23:00' },
|
||||||
|
{ dayOfWeek: 4, startTime: '18:00', endTime: '23:00' },
|
||||||
|
|
||||||
|
// Friday: 17:00 - 22:00 (earlier end)
|
||||||
|
{ dayOfWeek: 5, startTime: '17:00', endTime: '22:00' },
|
||||||
|
|
||||||
|
// Saturday: 10:00 - 18:00 (full day weekend)
|
||||||
|
{ dayOfWeek: 6, startTime: '10:00', endTime: '18:00' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const slot of timeSlotData) {
|
||||||
|
await db.insert(schema.timeSlots).values({
|
||||||
|
id: randomUUID(),
|
||||||
|
dayOfWeek: slot.dayOfWeek,
|
||||||
|
startTime: slot.startTime,
|
||||||
|
endTime: slot.endTime,
|
||||||
|
isActive: true,
|
||||||
|
createdAt: new Date(now),
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose) console.log(' ✓ Created time slots for all days');
|
||||||
|
} else {
|
||||||
|
if (verbose) console.log(' - Time slots already exist, skipping time slot creation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if announcements already exist
|
||||||
|
const existingAnnouncements = await db.select().from(schema.announcements);
|
||||||
|
|
||||||
|
if (existingAnnouncements.length === 0) {
|
||||||
|
// Create essential announcements
|
||||||
|
const essentialAnnouncements = [
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
title: 'Welcome to Table Tennis Booking System!',
|
||||||
|
content:
|
||||||
|
'Book your court times easily and manage your games efficiently. Check the time slots for availability and remember to arrive 5 minutes early.',
|
||||||
|
isActive: true,
|
||||||
|
priority: 'high' as const,
|
||||||
|
expiresAt: null,
|
||||||
|
createdAt: new Date(now),
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
title: 'Booking Guidelines',
|
||||||
|
content:
|
||||||
|
'Maximum booking duration is 2 hours. Please cancel bookings you cannot attend to allow others to use the courts.',
|
||||||
|
isActive: true,
|
||||||
|
priority: 'medium' as const,
|
||||||
|
expiresAt: null,
|
||||||
|
createdAt: new Date(now),
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await db.insert(schema.announcements).values(essentialAnnouncements);
|
||||||
|
|
||||||
|
if (verbose) console.log(' ✓ Created essential announcements');
|
||||||
|
} else {
|
||||||
|
if (verbose) console.log(' - Announcements already exist, skipping announcement creation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize monthly metrics if they don't exist
|
||||||
|
const currentMonth = new Date().toISOString().substring(0, 7);
|
||||||
|
const existingMetrics = await db
|
||||||
|
.select()
|
||||||
|
.from(schema.metrics)
|
||||||
|
.where(eq(schema.metrics.period, currentMonth))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (existingMetrics.length === 0) {
|
||||||
|
await db.insert(schema.metrics).values({
|
||||||
|
id: randomUUID(),
|
||||||
|
metricType: 'monthly_bookings',
|
||||||
|
period: currentMonth,
|
||||||
|
value: 0,
|
||||||
|
createdAt: new Date(now),
|
||||||
|
updatedAt: new Date(now),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (verbose) console.log(' ✓ Initialized monthly metrics');
|
||||||
|
} else {
|
||||||
|
if (verbose) console.log(' - Monthly metrics already exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Essential data seeded\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedSampleData(verbose: boolean) {
|
||||||
|
console.log('🎭 Seeding sample data...');
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const users = await db.select().from(schema.users);
|
||||||
|
const courts = await db.select().from(schema.courts);
|
||||||
|
|
||||||
|
const adminUser = users.find((u) => u.role === 'admin');
|
||||||
|
const regularUser = users.find((u) => u.role === 'user');
|
||||||
|
|
||||||
|
if (!adminUser || !regularUser) {
|
||||||
|
console.log('⚠️ No users found for sample data');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample bookings for the next few days
|
||||||
|
const today = new Date();
|
||||||
|
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
|
||||||
|
const dayAfter = new Date(today.getTime() + 48 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
const sampleBookings = [
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
userId: regularUser.id,
|
||||||
|
courtId: courts[0].id,
|
||||||
|
date: today.toISOString().split('T')[0],
|
||||||
|
startTime: '19:00',
|
||||||
|
endTime: '20:00',
|
||||||
|
status: 'active' as const,
|
||||||
|
notes: 'Regular evening practice session',
|
||||||
|
partnerName: 'John Smith',
|
||||||
|
createdAt: new Date(now - 2 * 60 * 60 * 1000),
|
||||||
|
updatedAt: new Date(now - 2 * 60 * 60 * 1000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
userId: regularUser.id,
|
||||||
|
courtId: courts[1]?.id || courts[0].id,
|
||||||
|
date: tomorrow.toISOString().split('T')[0],
|
||||||
|
startTime: '20:00',
|
||||||
|
endTime: '21:00',
|
||||||
|
status: 'active' as const,
|
||||||
|
notes: 'Tournament preparation',
|
||||||
|
partnerName: null,
|
||||||
|
createdAt: new Date(now - 1 * 60 * 60 * 1000),
|
||||||
|
updatedAt: new Date(now - 1 * 60 * 60 * 1000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
userId: adminUser.id,
|
||||||
|
courtId: courts[2]?.id || courts[0].id,
|
||||||
|
date: dayAfter.toISOString().split('T')[0],
|
||||||
|
startTime: '18:00',
|
||||||
|
endTime: '20:00',
|
||||||
|
status: 'active' as const,
|
||||||
|
notes: 'Staff training session',
|
||||||
|
partnerName: 'Staff Team',
|
||||||
|
createdAt: new Date(now - 30 * 60 * 1000),
|
||||||
|
updatedAt: new Date(now - 30 * 60 * 1000),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await db.insert(schema.bookings).values(sampleBookings);
|
||||||
|
|
||||||
|
// Sample activity logs
|
||||||
|
const sampleLogs = [
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
userId: adminUser.id,
|
||||||
|
action: 'login',
|
||||||
|
entityType: 'user',
|
||||||
|
entityId: adminUser.id,
|
||||||
|
details: JSON.stringify({
|
||||||
|
email: adminUser.email,
|
||||||
|
role: adminUser.role,
|
||||||
|
loginMethod: 'password',
|
||||||
|
}),
|
||||||
|
ipAddress: '192.168.1.100',
|
||||||
|
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
||||||
|
createdAt: new Date(now - 3 * 60 * 60 * 1000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
userId: regularUser.id,
|
||||||
|
action: 'create_booking',
|
||||||
|
entityType: 'booking',
|
||||||
|
entityId: sampleBookings[0].id,
|
||||||
|
details: JSON.stringify({
|
||||||
|
courtId: courts[0].id,
|
||||||
|
courtName: courts[0].name,
|
||||||
|
date: today.toISOString().split('T')[0],
|
||||||
|
startTime: '19:00',
|
||||||
|
endTime: '20:00',
|
||||||
|
}),
|
||||||
|
ipAddress: '192.168.1.101',
|
||||||
|
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
|
||||||
|
createdAt: new Date(now - 2 * 60 * 60 * 1000),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await db.insert(schema.activityLogs).values(sampleLogs);
|
||||||
|
|
||||||
|
// Additional announcements
|
||||||
|
const additionalAnnouncements = [
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
title: 'New Court Rules',
|
||||||
|
content:
|
||||||
|
'Please remember to clean up after your sessions and respect the time limits. Equipment should be returned to the storage area.',
|
||||||
|
isActive: true,
|
||||||
|
priority: 'medium' as const,
|
||||||
|
expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000), // 1 week from now
|
||||||
|
createdAt: new Date(now - 4 * 60 * 60 * 1000),
|
||||||
|
updatedAt: new Date(now - 4 * 60 * 60 * 1000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
title: 'Tournament Sign-ups Open!',
|
||||||
|
content:
|
||||||
|
'The annual table tennis tournament sign-ups are now open! Register by the end of this month. Prizes for winners in each category.',
|
||||||
|
isActive: true,
|
||||||
|
priority: 'high' as const,
|
||||||
|
expiresAt: new Date(now + 30 * 24 * 60 * 60 * 1000), // 30 days from now
|
||||||
|
createdAt: new Date(now - 24 * 60 * 60 * 1000),
|
||||||
|
updatedAt: new Date(now - 24 * 60 * 60 * 1000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: randomUUID(),
|
||||||
|
title: 'Equipment Maintenance',
|
||||||
|
content:
|
||||||
|
'New paddles and balls have been added to the equipment collection. Old equipment will be replaced gradually.',
|
||||||
|
isActive: true,
|
||||||
|
priority: 'low' as const,
|
||||||
|
expiresAt: new Date(now + 14 * 24 * 60 * 60 * 1000), // 2 weeks from now
|
||||||
|
createdAt: new Date(now - 6 * 60 * 60 * 1000),
|
||||||
|
updatedAt: new Date(now - 6 * 60 * 60 * 1000),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await db.insert(schema.announcements).values(additionalAnnouncements);
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
console.log(` ✓ Created ${sampleBookings.length} sample bookings`);
|
||||||
|
console.log(` ✓ Created ${sampleLogs.length} activity logs`);
|
||||||
|
console.log(` ✓ Created ${additionalAnnouncements.length} additional announcements`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Sample data seeded\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function printDatabaseSummary() {
|
||||||
|
console.log('📊 Database Summary:');
|
||||||
|
console.log('═══════════════════════════════════════\n');
|
||||||
|
|
||||||
|
const users = await db.select().from(schema.users);
|
||||||
|
const courts = await db.select().from(schema.courts);
|
||||||
|
const bookings = await db.select().from(schema.bookings);
|
||||||
|
const announcements = await db.select().from(schema.announcements);
|
||||||
|
const timeSlots = await db.select().from(schema.timeSlots);
|
||||||
|
const settings = await db.select().from(schema.settings);
|
||||||
|
|
||||||
|
console.log(`👥 Users: ${users.length}`);
|
||||||
|
users.forEach((user) => {
|
||||||
|
console.log(` • ${user.name} ${user.surname} (${user.email}) - ${user.role}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\n🏓 Courts: ${courts.length}`);
|
||||||
|
courts.forEach((court) => {
|
||||||
|
console.log(` • ${court.name} - ${court.isActive ? 'Active' : 'Inactive'}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\n📅 Bookings: ${bookings.length}`);
|
||||||
|
if (bookings.length > 0) {
|
||||||
|
console.log(' Recent bookings:');
|
||||||
|
bookings.slice(0, 3).forEach((booking) => {
|
||||||
|
console.log(` • ${booking.date} ${booking.startTime}-${booking.endTime} (${booking.status})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n📢 Announcements: ${announcements.length}`);
|
||||||
|
const activeAnnouncements = announcements.filter((a) => a.isActive);
|
||||||
|
console.log(` • Active: ${activeAnnouncements.length}`);
|
||||||
|
|
||||||
|
console.log(`\n⏰ Time Slots: ${timeSlots.length}`);
|
||||||
|
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||||
|
timeSlots.forEach((slot) => {
|
||||||
|
console.log(` • ${dayNames[slot.dayOfWeek]}: ${slot.startTime}-${slot.endTime}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\n⚙️ Settings: ${settings.length} configured`);
|
||||||
|
|
||||||
|
console.log('\n💡 Login Credentials:');
|
||||||
|
console.log(' Admin: admin@tabletennis.com / admin123');
|
||||||
|
console.log(' User: user@tabletennis.com / user123');
|
||||||
|
|
||||||
|
console.log('\n🚀 Ready to start! Run: npm run dev');
|
||||||
|
console.log('═══════════════════════════════════════\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
function parseArgs() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const options: SetupOptions = {};
|
||||||
|
|
||||||
|
if (args.includes('--reset') || args.includes('-r')) {
|
||||||
|
options.reset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.includes('--no-sample-data') || args.includes('--essential-only')) {
|
||||||
|
options.seedData = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.includes('--verbose') || args.includes('-v')) {
|
||||||
|
options.verbose = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.includes('--help') || args.includes('-h')) {
|
||||||
|
console.log(`
|
||||||
|
Table Tennis Booking System - Database Setup
|
||||||
|
|
||||||
|
Usage: tsx scripts/setup-database.ts [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--reset, -r Reset/drop all tables before setup
|
||||||
|
--no-sample-data Only seed essential data (no sample bookings/logs)
|
||||||
|
--essential-only Same as --no-sample-data
|
||||||
|
--verbose, -v Show detailed output
|
||||||
|
--help, -h Show this help message
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
tsx scripts/setup-database.ts # Full setup with sample data
|
||||||
|
tsx scripts/setup-database.ts --reset # Reset and full setup
|
||||||
|
tsx scripts/setup-database.ts --essential-only # Only essential data
|
||||||
|
tsx scripts/setup-database.ts --reset --verbose # Reset with detailed output
|
||||||
|
`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main execution
|
||||||
|
if (require.main === module) {
|
||||||
|
const options = parseArgs();
|
||||||
|
|
||||||
|
setupDatabase(options)
|
||||||
|
.then(() => {
|
||||||
|
console.log('🎉 Database setup completed successfully!');
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('💥 Database setup failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { setupDatabase };
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Test script to verify Irish localization settings
|
||||||
|
console.log('🇮🇪 Testing Irish Localization Settings\n');
|
||||||
|
|
||||||
|
console.log('1. Week starts on Monday (Irish standard)');
|
||||||
|
console.log(' JavaScript getDay() values:');
|
||||||
|
console.log(' Sunday = 0, Monday = 1, ..., Saturday = 6');
|
||||||
|
console.log(' Irish display order should be: Monday, Tuesday, ..., Sunday\n');
|
||||||
|
|
||||||
|
// Test date formatting
|
||||||
|
const testDate = new Date('2025-09-25'); // This is a Thursday
|
||||||
|
console.log('2. Date Formatting Test:');
|
||||||
|
console.log(` Test date: ${testDate.toDateString()}`);
|
||||||
|
console.log(
|
||||||
|
` Irish format (en-IE): ${testDate.toLocaleDateString('en-IE', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
})}`
|
||||||
|
);
|
||||||
|
console.log(` Irish short format: ${testDate.toLocaleDateString('en-IE', { weekday: 'short' })}`);
|
||||||
|
|
||||||
|
console.log('\n3. Day of Week Conversion:');
|
||||||
|
const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||||
|
for (let jsDay = 0; jsDay <= 6; jsDay++) {
|
||||||
|
const irishDisplayOrder = jsDay === 0 ? 6 : jsDay - 1; // Convert Sunday(0) to position 6, others shift down
|
||||||
|
console.log(` JS Day ${jsDay} (${daysOfWeek[jsDay]}) -> Irish position ${irishDisplayOrder}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n4. Week Structure for Admin Panel:');
|
||||||
|
const irishWeekOrder = [1, 2, 3, 4, 5, 6, 0]; // Monday through Sunday in JS values
|
||||||
|
irishWeekOrder.forEach((jsDay, displayIndex) => {
|
||||||
|
console.log(` Display position ${displayIndex}: ${daysOfWeek[jsDay]} (JS day ${jsDay})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n✅ Irish localization configuration complete!');
|
||||||
|
console.log('📅 Calendar will now start with Monday');
|
||||||
|
console.log('🇮🇪 All dates will use en-IE locale format');
|
||||||
|
console.log('⏰ 24-hour time format maintained');
|
||||||
Reference in New Issue
Block a user