40c56770a2
- Added AdminBlocksManagement component for managing court blocks. - Implemented functionality to create, edit, and delete blocks. - Integrated fetching of courts and blocks from the API. - Added validation for block creation and editing forms. - Enhanced UI with responsive design for mobile and desktop views. - Created database migration for court_blocks table and updated users table with theme_preference.
160 lines
6.9 KiB
TypeScript
160 lines
6.9 KiB
TypeScript
import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core';
|
|
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
|
|
import { z } from 'zod';
|
|
|
|
// Users table
|
|
export const users = sqliteTable('users', {
|
|
id: text('id').primaryKey(),
|
|
email: text('email').notNull().unique(),
|
|
name: text('name').notNull(),
|
|
surname: text('surname').notNull(),
|
|
password: text('password').notNull(),
|
|
role: text('role', { enum: ['user', 'admin'] })
|
|
.notNull()
|
|
.default('user'),
|
|
themePreference: text('theme_preference', { enum: ['light', 'dark', 'system'] })
|
|
.notNull()
|
|
.default('system'),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
});
|
|
|
|
// Courts table
|
|
export const courts = sqliteTable('courts', {
|
|
id: text('id').primaryKey(),
|
|
name: text('name').notNull(),
|
|
isActive: integer('is_active', { mode: 'boolean' }).notNull().default(true),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
});
|
|
|
|
// Settings table for admin configuration
|
|
export const settings = sqliteTable('settings', {
|
|
id: text('id').primaryKey(),
|
|
key: text('key').notNull().unique(),
|
|
value: text('value').notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
});
|
|
|
|
// Time slots configuration
|
|
export const timeSlots = sqliteTable('time_slots', {
|
|
id: text('id').primaryKey(),
|
|
dayOfWeek: integer('day_of_week').notNull(), // 0 = Sunday, 1 = Monday, etc.
|
|
startTime: text('start_time').notNull(), // Format: "HH:MM"
|
|
endTime: text('end_time').notNull(), // Format: "HH:MM"
|
|
isActive: integer('is_active', { mode: 'boolean' }).notNull().default(true),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
});
|
|
|
|
// Bookings table
|
|
export const bookings = sqliteTable('bookings', {
|
|
id: text('id').primaryKey(),
|
|
userId: text('user_id')
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: 'cascade' }),
|
|
courtId: text('court_id')
|
|
.notNull()
|
|
.references(() => courts.id, { onDelete: 'cascade' }),
|
|
date: text('date').notNull(), // Format: "YYYY-MM-DD"
|
|
startTime: text('start_time').notNull(), // Format: "HH:MM"
|
|
endTime: text('end_time').notNull(), // Format: "HH:MM"
|
|
status: text('status', { enum: ['active', 'cancelled'] })
|
|
.notNull()
|
|
.default('active'),
|
|
notes: text('notes'),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
});
|
|
|
|
// Announcements table
|
|
export const announcements = sqliteTable('announcements', {
|
|
id: text('id').primaryKey(),
|
|
title: text('title').notNull(),
|
|
content: text('content').notNull(),
|
|
isActive: integer('is_active', { mode: 'boolean' }).notNull().default(true),
|
|
priority: text('priority', { enum: ['low', 'medium', 'high'] })
|
|
.notNull()
|
|
.default('medium'),
|
|
expiresAt: integer('expires_at', { mode: 'timestamp' }),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
});
|
|
|
|
// Activity logs for admin transparency
|
|
export const activityLogs = sqliteTable('activity_logs', {
|
|
id: text('id').primaryKey(),
|
|
userId: text('user_id').references(() => users.id, { onDelete: 'set null' }),
|
|
action: text('action').notNull(),
|
|
entityType: text('entity_type').notNull(),
|
|
entityId: text('entity_id'),
|
|
details: text('details'), // JSON string
|
|
ipAddress: text('ip_address'),
|
|
userAgent: text('user_agent'),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
});
|
|
|
|
// Metrics table for tracking monthly statistics
|
|
export const metrics = sqliteTable('metrics', {
|
|
id: text('id').primaryKey(),
|
|
metricType: text('metric_type').notNull(), // e.g., 'monthly_bookings'
|
|
period: text('period').notNull(), // e.g., '2025-09' for September 2025
|
|
value: integer('value').notNull().default(0),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
|
});
|
|
|
|
// Court blocks table for admin-managed closures (tournaments, maintenance, etc.)
|
|
export const courtBlocks = sqliteTable('court_blocks', {
|
|
id: text('id').primaryKey(),
|
|
courtId: text('court_id').references(() => courts.id, { onDelete: 'cascade' }), // NULL means all courts
|
|
date: text('date').notNull(), // Format: "YYYY-MM-DD"
|
|
startTime: text('start_time').notNull(), // Format: "HH:MM"
|
|
endTime: text('end_time').notNull(), // Format: "HH:MM"
|
|
reason: text('reason').notNull(), // e.g., "Tournament", "AGM", "Maintenance"
|
|
createdBy: text('created_by')
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: 'cascade' }),
|
|
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
|
});
|
|
|
|
// Zod schemas for validation
|
|
export const insertUserSchema = createInsertSchema(users);
|
|
export const selectUserSchema = createSelectSchema(users);
|
|
export const insertCourtSchema = createInsertSchema(courts);
|
|
export const selectCourtSchema = createSelectSchema(courts);
|
|
export const insertBookingSchema = createInsertSchema(bookings);
|
|
export const selectBookingSchema = createSelectSchema(bookings);
|
|
export const insertAnnouncementSchema = createInsertSchema(announcements);
|
|
export const selectAnnouncementSchema = createSelectSchema(announcements);
|
|
export const insertTimeSlotSchema = createInsertSchema(timeSlots);
|
|
export const selectTimeSlotSchema = createSelectSchema(timeSlots);
|
|
export const insertSettingSchema = createInsertSchema(settings);
|
|
export const selectSettingSchema = createSelectSchema(settings);
|
|
export const insertActivityLogSchema = createInsertSchema(activityLogs);
|
|
export const selectActivityLogSchema = createSelectSchema(activityLogs);
|
|
export const insertMetricSchema = createInsertSchema(metrics);
|
|
export const selectMetricSchema = createSelectSchema(metrics);
|
|
export const insertCourtBlockSchema = createInsertSchema(courtBlocks);
|
|
export const selectCourtBlockSchema = createSelectSchema(courtBlocks);
|
|
|
|
// Types
|
|
export type User = typeof users.$inferSelect;
|
|
export type NewUser = typeof users.$inferInsert;
|
|
export type Court = typeof courts.$inferSelect;
|
|
export type NewCourt = typeof courts.$inferInsert;
|
|
export type Booking = typeof bookings.$inferSelect;
|
|
export type NewBooking = typeof bookings.$inferInsert;
|
|
export type Announcement = typeof announcements.$inferSelect;
|
|
export type NewAnnouncement = typeof announcements.$inferInsert;
|
|
export type TimeSlot = typeof timeSlots.$inferSelect;
|
|
export type NewTimeSlot = typeof timeSlots.$inferInsert;
|
|
export type Setting = typeof settings.$inferSelect;
|
|
export type NewSetting = typeof settings.$inferInsert;
|
|
export type ActivityLog = typeof activityLogs.$inferSelect;
|
|
export type NewActivityLog = typeof activityLogs.$inferInsert;
|
|
export type Metric = typeof metrics.$inferSelect;
|
|
export type NewMetric = typeof metrics.$inferInsert;
|
|
export type CourtBlock = typeof courtBlocks.$inferSelect;
|
|
export type NewCourtBlock = typeof courtBlocks.$inferInsert;
|