Enhance calendar view with resource-aware rendering
Adds support for filtering events and rendering across multiple views with resource-specific context Improves event and date rendering to handle resource-based filtering Introduces day view and extends existing calendar infrastructure to support more flexible view configurations
This commit is contained in:
parent
6fc9be9534
commit
7f6279a6f3
17 changed files with 570 additions and 38 deletions
|
|
@ -10,7 +10,7 @@ import { IStore } from './IStore';
|
|||
*/
|
||||
export class IndexedDBContext {
|
||||
private static readonly DB_NAME = 'CalendarV2DB';
|
||||
private static readonly DB_VERSION = 1;
|
||||
private static readonly DB_VERSION = 2;
|
||||
|
||||
private db: IDBDatabase | null = null;
|
||||
private initialized: boolean = false;
|
||||
|
|
|
|||
75
src/v2/storage/bookings/BookingService.ts
Normal file
75
src/v2/storage/bookings/BookingService.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { IBooking, EntityType, IEventBus, BookingStatus } from '../../types/CalendarTypes';
|
||||
import { BookingStore } from './BookingStore';
|
||||
import { BaseEntityService } from '../BaseEntityService';
|
||||
import { IndexedDBContext } from '../IndexedDBContext';
|
||||
|
||||
/**
|
||||
* BookingService - CRUD operations for bookings in IndexedDB
|
||||
*/
|
||||
export class BookingService extends BaseEntityService<IBooking> {
|
||||
readonly storeName = BookingStore.STORE_NAME;
|
||||
readonly entityType: EntityType = 'Booking';
|
||||
|
||||
constructor(context: IndexedDBContext, eventBus: IEventBus) {
|
||||
super(context, eventBus);
|
||||
}
|
||||
|
||||
protected serialize(booking: IBooking): unknown {
|
||||
return {
|
||||
...booking,
|
||||
createdAt: booking.createdAt.toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
protected deserialize(data: unknown): IBooking {
|
||||
const raw = data as Record<string, unknown>;
|
||||
return {
|
||||
...raw,
|
||||
createdAt: new Date(raw.createdAt as string)
|
||||
} as IBooking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bookings for a customer
|
||||
*/
|
||||
async getByCustomer(customerId: string): Promise<IBooking[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db.transaction([this.storeName], 'readonly');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
const index = store.index('customerId');
|
||||
const request = index.getAll(customerId);
|
||||
|
||||
request.onsuccess = () => {
|
||||
const data = request.result as unknown[];
|
||||
const bookings = data.map(item => this.deserialize(item));
|
||||
resolve(bookings);
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`Failed to get bookings for customer ${customerId}: ${request.error}`));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bookings by status
|
||||
*/
|
||||
async getByStatus(status: BookingStatus): Promise<IBooking[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db.transaction([this.storeName], 'readonly');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
const index = store.index('status');
|
||||
const request = index.getAll(status);
|
||||
|
||||
request.onsuccess = () => {
|
||||
const data = request.result as unknown[];
|
||||
const bookings = data.map(item => this.deserialize(item));
|
||||
resolve(bookings);
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`Failed to get bookings with status ${status}: ${request.error}`));
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
18
src/v2/storage/bookings/BookingStore.ts
Normal file
18
src/v2/storage/bookings/BookingStore.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { IStore } from '../IStore';
|
||||
|
||||
/**
|
||||
* BookingStore - IndexedDB ObjectStore definition for bookings
|
||||
*/
|
||||
export class BookingStore implements IStore {
|
||||
static readonly STORE_NAME = 'bookings';
|
||||
readonly storeName = BookingStore.STORE_NAME;
|
||||
|
||||
create(db: IDBDatabase): void {
|
||||
const store = db.createObjectStore(BookingStore.STORE_NAME, { keyPath: 'id' });
|
||||
|
||||
store.createIndex('customerId', 'customerId', { unique: false });
|
||||
store.createIndex('status', 'status', { unique: false });
|
||||
store.createIndex('syncStatus', 'syncStatus', { unique: false });
|
||||
store.createIndex('createdAt', 'createdAt', { unique: false });
|
||||
}
|
||||
}
|
||||
46
src/v2/storage/customers/CustomerService.ts
Normal file
46
src/v2/storage/customers/CustomerService.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { ICustomer, EntityType, IEventBus } from '../../types/CalendarTypes';
|
||||
import { CustomerStore } from './CustomerStore';
|
||||
import { BaseEntityService } from '../BaseEntityService';
|
||||
import { IndexedDBContext } from '../IndexedDBContext';
|
||||
|
||||
/**
|
||||
* CustomerService - CRUD operations for customers in IndexedDB
|
||||
*/
|
||||
export class CustomerService extends BaseEntityService<ICustomer> {
|
||||
readonly storeName = CustomerStore.STORE_NAME;
|
||||
readonly entityType: EntityType = 'Customer';
|
||||
|
||||
constructor(context: IndexedDBContext, eventBus: IEventBus) {
|
||||
super(context, eventBus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search customers by name (case-insensitive contains)
|
||||
*/
|
||||
async searchByName(query: string): Promise<ICustomer[]> {
|
||||
const all = await this.getAll();
|
||||
const lowerQuery = query.toLowerCase();
|
||||
return all.filter(c => c.name.toLowerCase().includes(lowerQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find customer by phone
|
||||
*/
|
||||
async getByPhone(phone: string): Promise<ICustomer | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db.transaction([this.storeName], 'readonly');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
const index = store.index('phone');
|
||||
const request = index.get(phone);
|
||||
|
||||
request.onsuccess = () => {
|
||||
const data = request.result;
|
||||
resolve(data ? (data as ICustomer) : null);
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`Failed to find customer by phone ${phone}: ${request.error}`));
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
17
src/v2/storage/customers/CustomerStore.ts
Normal file
17
src/v2/storage/customers/CustomerStore.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { IStore } from '../IStore';
|
||||
|
||||
/**
|
||||
* CustomerStore - IndexedDB ObjectStore definition for customers
|
||||
*/
|
||||
export class CustomerStore implements IStore {
|
||||
static readonly STORE_NAME = 'customers';
|
||||
readonly storeName = CustomerStore.STORE_NAME;
|
||||
|
||||
create(db: IDBDatabase): void {
|
||||
const store = db.createObjectStore(CustomerStore.STORE_NAME, { keyPath: 'id' });
|
||||
|
||||
store.createIndex('name', 'name', { unique: false });
|
||||
store.createIndex('phone', 'phone', { unique: false });
|
||||
store.createIndex('syncStatus', 'syncStatus', { unique: false });
|
||||
}
|
||||
}
|
||||
45
src/v2/storage/resources/ResourceService.ts
Normal file
45
src/v2/storage/resources/ResourceService.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { IResource, EntityType, IEventBus } from '../../types/CalendarTypes';
|
||||
import { ResourceStore } from './ResourceStore';
|
||||
import { BaseEntityService } from '../BaseEntityService';
|
||||
import { IndexedDBContext } from '../IndexedDBContext';
|
||||
|
||||
/**
|
||||
* ResourceService - CRUD operations for resources in IndexedDB
|
||||
*/
|
||||
export class ResourceService extends BaseEntityService<IResource> {
|
||||
readonly storeName = ResourceStore.STORE_NAME;
|
||||
readonly entityType: EntityType = 'Resource';
|
||||
|
||||
constructor(context: IndexedDBContext, eventBus: IEventBus) {
|
||||
super(context, eventBus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active resources
|
||||
*/
|
||||
async getActive(): Promise<IResource[]> {
|
||||
const all = await this.getAll();
|
||||
return all.filter(r => r.isActive !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resources by type
|
||||
*/
|
||||
async getByType(type: string): Promise<IResource[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db.transaction([this.storeName], 'readonly');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
const index = store.index('type');
|
||||
const request = index.getAll(type);
|
||||
|
||||
request.onsuccess = () => {
|
||||
const data = request.result as IResource[];
|
||||
resolve(data);
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`Failed to get resources by type ${type}: ${request.error}`));
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
17
src/v2/storage/resources/ResourceStore.ts
Normal file
17
src/v2/storage/resources/ResourceStore.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { IStore } from '../IStore';
|
||||
|
||||
/**
|
||||
* ResourceStore - IndexedDB ObjectStore definition for resources
|
||||
*/
|
||||
export class ResourceStore implements IStore {
|
||||
static readonly STORE_NAME = 'resources';
|
||||
readonly storeName = ResourceStore.STORE_NAME;
|
||||
|
||||
create(db: IDBDatabase): void {
|
||||
const store = db.createObjectStore(ResourceStore.STORE_NAME, { keyPath: 'id' });
|
||||
|
||||
store.createIndex('type', 'type', { unique: false });
|
||||
store.createIndex('syncStatus', 'syncStatus', { unique: false });
|
||||
store.createIndex('isActive', 'isActive', { unique: false });
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue