Refactor settings model to separate record-based approach
Restructures tenant settings to use individual records instead of a single document Decouples settings sections into separate typed interfaces with unique IDs Modifies data loading and service methods to support new record-based settings Updates mock data and repository to align with new settings structure
This commit is contained in:
parent
b2c81dc163
commit
9f360237cf
5 changed files with 97 additions and 69 deletions
|
|
@ -38,7 +38,7 @@ import { DepartmentStore } from './storage/departments/DepartmentStore';
|
||||||
import { DepartmentService } from './storage/departments/DepartmentService';
|
import { DepartmentService } from './storage/departments/DepartmentService';
|
||||||
import { SettingsStore } from './storage/settings/SettingsStore';
|
import { SettingsStore } from './storage/settings/SettingsStore';
|
||||||
import { SettingsService } from './storage/settings/SettingsService';
|
import { SettingsService } from './storage/settings/SettingsService';
|
||||||
import { ITenantSettings } from './types/SettingsTypes';
|
import { TenantSetting } from './types/SettingsTypes';
|
||||||
import { ViewConfigStore } from './storage/viewconfigs/ViewConfigStore';
|
import { ViewConfigStore } from './storage/viewconfigs/ViewConfigStore';
|
||||||
import { ViewConfigService } from './storage/viewconfigs/ViewConfigService';
|
import { ViewConfigService } from './storage/viewconfigs/ViewConfigService';
|
||||||
import { ViewConfig } from './core/ViewConfig';
|
import { ViewConfig } from './core/ViewConfig';
|
||||||
|
|
@ -150,7 +150,7 @@ export function createV2Container(): Container {
|
||||||
builder.registerType(DepartmentService).as<IEntityService<ISync>>();
|
builder.registerType(DepartmentService).as<IEntityService<ISync>>();
|
||||||
builder.registerType(DepartmentService).as<DepartmentService>();
|
builder.registerType(DepartmentService).as<DepartmentService>();
|
||||||
|
|
||||||
builder.registerType(SettingsService).as<IEntityService<ITenantSettings>>();
|
builder.registerType(SettingsService).as<IEntityService<TenantSetting>>();
|
||||||
builder.registerType(SettingsService).as<IEntityService<ISync>>();
|
builder.registerType(SettingsService).as<IEntityService<ISync>>();
|
||||||
builder.registerType(SettingsService).as<SettingsService>();
|
builder.registerType(SettingsService).as<SettingsService>();
|
||||||
|
|
||||||
|
|
@ -180,7 +180,7 @@ export function createV2Container(): Container {
|
||||||
builder.registerType(MockDepartmentRepository).as<IApiRepository<IDepartment>>();
|
builder.registerType(MockDepartmentRepository).as<IApiRepository<IDepartment>>();
|
||||||
builder.registerType(MockDepartmentRepository).as<IApiRepository<ISync>>();
|
builder.registerType(MockDepartmentRepository).as<IApiRepository<ISync>>();
|
||||||
|
|
||||||
builder.registerType(MockSettingsRepository).as<IApiRepository<ITenantSettings>>();
|
builder.registerType(MockSettingsRepository).as<IApiRepository<TenantSetting>>();
|
||||||
builder.registerType(MockSettingsRepository).as<IApiRepository<ISync>>();
|
builder.registerType(MockSettingsRepository).as<IApiRepository<ISync>>();
|
||||||
|
|
||||||
builder.registerType(MockViewConfigRepository).as<IApiRepository<ViewConfig>>();
|
builder.registerType(MockViewConfigRepository).as<IApiRepository<ViewConfig>>();
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import { EntityType } from '../types/CalendarTypes';
|
import { EntityType } from '../types/CalendarTypes';
|
||||||
import { ITenantSettings } from '../types/SettingsTypes';
|
import { TenantSetting } from '../types/SettingsTypes';
|
||||||
import { IApiRepository } from './IApiRepository';
|
import { IApiRepository } from './IApiRepository';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MockSettingsRepository - Loads tenant settings from local JSON file
|
* MockSettingsRepository - Loads tenant settings from local JSON file
|
||||||
*
|
*
|
||||||
* Settings is a single document, but we wrap it in an array to match
|
* Settings are stored as separate records per section (workweek, grid, etc.).
|
||||||
* the IApiRepository interface used by DataSeeder.
|
* The JSON file is already an array of TenantSetting records.
|
||||||
*/
|
*/
|
||||||
export class MockSettingsRepository implements IApiRepository<ITenantSettings> {
|
export class MockSettingsRepository implements IApiRepository<TenantSetting> {
|
||||||
public readonly entityType: EntityType = 'Settings';
|
public readonly entityType: EntityType = 'Settings';
|
||||||
private readonly dataUrl = 'data/tenant-settings.json';
|
private readonly dataUrl = 'data/tenant-settings.json';
|
||||||
|
|
||||||
public async fetchAll(): Promise<ITenantSettings[]> {
|
public async fetchAll(): Promise<TenantSetting[]> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(this.dataUrl);
|
const response = await fetch(this.dataUrl);
|
||||||
|
|
||||||
|
|
@ -20,24 +20,23 @@ export class MockSettingsRepository implements IApiRepository<ITenantSettings> {
|
||||||
throw new Error(`Failed to load tenant settings: ${response.status} ${response.statusText}`);
|
throw new Error(`Failed to load tenant settings: ${response.status} ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawData = await response.json();
|
const settings: TenantSetting[] = await response.json();
|
||||||
// Ensure syncStatus is set
|
// Ensure syncStatus is set on each record
|
||||||
const settings: ITenantSettings = {
|
return settings.map(s => ({
|
||||||
...rawData,
|
...s,
|
||||||
syncStatus: rawData.syncStatus || 'synced'
|
syncStatus: s.syncStatus || 'synced'
|
||||||
};
|
}));
|
||||||
return [settings];
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load tenant settings:', error);
|
console.error('Failed to load tenant settings:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sendCreate(_settings: ITenantSettings): Promise<ITenantSettings> {
|
public async sendCreate(_settings: TenantSetting): Promise<TenantSetting> {
|
||||||
throw new Error('MockSettingsRepository does not support sendCreate. Mock data is read-only.');
|
throw new Error('MockSettingsRepository does not support sendCreate. Mock data is read-only.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sendUpdate(_id: string, _updates: Partial<ITenantSettings>): Promise<ITenantSettings> {
|
public async sendUpdate(_id: string, _updates: Partial<TenantSetting>): Promise<TenantSetting> {
|
||||||
throw new Error('MockSettingsRepository does not support sendUpdate. Mock data is read-only.');
|
throw new Error('MockSettingsRepository does not support sendUpdate. Mock data is read-only.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
import { EntityType, IEventBus } from '../../types/CalendarTypes';
|
import { EntityType, IEventBus } from '../../types/CalendarTypes';
|
||||||
import { ITenantSettings, IWorkweekPreset } from '../../types/SettingsTypes';
|
import {
|
||||||
|
TenantSetting,
|
||||||
|
IWorkweekSettings,
|
||||||
|
IGridSettings,
|
||||||
|
ITimeFormatSettings,
|
||||||
|
IViewSettings,
|
||||||
|
IWorkweekPreset,
|
||||||
|
SettingsIds
|
||||||
|
} from '../../types/SettingsTypes';
|
||||||
import { SettingsStore } from './SettingsStore';
|
import { SettingsStore } from './SettingsStore';
|
||||||
import { BaseEntityService } from '../BaseEntityService';
|
import { BaseEntityService } from '../BaseEntityService';
|
||||||
import { IndexedDBContext } from '../IndexedDBContext';
|
import { IndexedDBContext } from '../IndexedDBContext';
|
||||||
|
|
||||||
/**
|
|
||||||
* Default settings ID - single document per tenant
|
|
||||||
*/
|
|
||||||
const TENANT_SETTINGS_ID = 'tenant-settings';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SettingsService - CRUD operations for tenant settings
|
* SettingsService - CRUD operations for tenant settings
|
||||||
*
|
*
|
||||||
* Settings are stored as a single document with sections.
|
* Settings are stored as separate records per section.
|
||||||
* This service provides convenience methods for accessing specific sections.
|
* This service provides typed methods for accessing specific settings.
|
||||||
*/
|
*/
|
||||||
export class SettingsService extends BaseEntityService<ITenantSettings> {
|
export class SettingsService extends BaseEntityService<TenantSetting> {
|
||||||
readonly storeName = SettingsStore.STORE_NAME;
|
readonly storeName = SettingsStore.STORE_NAME;
|
||||||
readonly entityType: EntityType = 'Settings';
|
readonly entityType: EntityType = 'Settings';
|
||||||
|
|
||||||
|
|
@ -24,37 +27,57 @@ export class SettingsService extends BaseEntityService<ITenantSettings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the tenant settings document
|
* Get workweek settings
|
||||||
* Returns null if not yet loaded from backend
|
|
||||||
*/
|
*/
|
||||||
async getSettings(): Promise<ITenantSettings | null> {
|
async getWorkweekSettings(): Promise<IWorkweekSettings | null> {
|
||||||
return this.get(TENANT_SETTINGS_ID);
|
return this.get(SettingsIds.WORKWEEK) as Promise<IWorkweekSettings | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get grid settings
|
||||||
|
*/
|
||||||
|
async getGridSettings(): Promise<IGridSettings | null> {
|
||||||
|
return this.get(SettingsIds.GRID) as Promise<IGridSettings | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get time format settings
|
||||||
|
*/
|
||||||
|
async getTimeFormatSettings(): Promise<ITimeFormatSettings | null> {
|
||||||
|
return this.get(SettingsIds.TIME_FORMAT) as Promise<ITimeFormatSettings | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get view settings
|
||||||
|
*/
|
||||||
|
async getViewSettings(): Promise<IViewSettings | null> {
|
||||||
|
return this.get(SettingsIds.VIEWS) as Promise<IViewSettings | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get workweek preset by ID
|
* Get workweek preset by ID
|
||||||
*/
|
*/
|
||||||
async getWorkweekPreset(presetId: string): Promise<IWorkweekPreset | null> {
|
async getWorkweekPreset(presetId: string): Promise<IWorkweekPreset | null> {
|
||||||
const settings = await this.getSettings();
|
const settings = await this.getWorkweekSettings();
|
||||||
if (!settings) return null;
|
if (!settings) return null;
|
||||||
return settings.workweek.presets[presetId] || null;
|
return settings.presets[presetId] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the default workweek preset
|
* Get the default workweek preset
|
||||||
*/
|
*/
|
||||||
async getDefaultWorkweekPreset(): Promise<IWorkweekPreset | null> {
|
async getDefaultWorkweekPreset(): Promise<IWorkweekPreset | null> {
|
||||||
const settings = await this.getSettings();
|
const settings = await this.getWorkweekSettings();
|
||||||
if (!settings) return null;
|
if (!settings) return null;
|
||||||
return settings.workweek.presets[settings.workweek.defaultPreset] || null;
|
return settings.presets[settings.defaultPreset] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all available workweek presets
|
* Get all available workweek presets
|
||||||
*/
|
*/
|
||||||
async getWorkweekPresets(): Promise<IWorkweekPreset[]> {
|
async getWorkweekPresets(): Promise<IWorkweekPreset[]> {
|
||||||
const settings = await this.getSettings();
|
const settings = await this.getWorkweekSettings();
|
||||||
if (!settings) return [];
|
if (!settings) return [];
|
||||||
return Object.values(settings.workweek.presets);
|
return Object.values(settings.presets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
*
|
*
|
||||||
* Settings are tenant-specific configuration that comes from the backend
|
* Settings are tenant-specific configuration that comes from the backend
|
||||||
* and is stored in IndexedDB for offline access.
|
* and is stored in IndexedDB for offline access.
|
||||||
|
*
|
||||||
|
* Each settings section is stored as a separate record with its own id.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ISync } from './CalendarTypes';
|
import { ISync } from './CalendarTypes';
|
||||||
|
|
@ -18,18 +20,20 @@ export interface IWorkweekPreset {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Workweek settings section
|
* Workweek settings - stored as separate record
|
||||||
*/
|
*/
|
||||||
export interface IWorkweekSettings {
|
export interface IWorkweekSettings extends ISync {
|
||||||
|
id: 'workweek';
|
||||||
presets: Record<string, IWorkweekPreset>;
|
presets: Record<string, IWorkweekPreset>;
|
||||||
defaultPreset: string;
|
defaultPreset: string;
|
||||||
firstDayOfWeek: number; // ISO: 1=Monday
|
firstDayOfWeek: number; // ISO: 1=Monday
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grid display settings section
|
* Grid display settings - stored as separate record
|
||||||
*/
|
*/
|
||||||
export interface IGridSettings {
|
export interface IGridSettings extends ISync {
|
||||||
|
id: 'grid';
|
||||||
dayStartHour: number;
|
dayStartHour: number;
|
||||||
dayEndHour: number;
|
dayEndHour: number;
|
||||||
workStartHour: number;
|
workStartHour: number;
|
||||||
|
|
@ -39,34 +43,35 @@ export interface IGridSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time format settings section
|
* Time format settings - stored as separate record
|
||||||
*/
|
*/
|
||||||
export interface ITimeFormatSettings {
|
export interface ITimeFormatSettings extends ISync {
|
||||||
|
id: 'timeFormat';
|
||||||
timezone: string;
|
timezone: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
use24HourFormat: boolean;
|
use24HourFormat: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View settings section
|
* View settings - stored as separate record
|
||||||
*/
|
*/
|
||||||
export interface IViewSettings {
|
export interface IViewSettings extends ISync {
|
||||||
|
id: 'views';
|
||||||
availableViews: string[];
|
availableViews: string[];
|
||||||
defaultView: string;
|
defaultView: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ITenantSettings - Complete tenant configuration
|
* Union type for all tenant settings records
|
||||||
*
|
|
||||||
* Single document stored in IndexedDB 'settings' store.
|
|
||||||
* Sections can be extended as needed without schema changes.
|
|
||||||
*/
|
*/
|
||||||
export interface ITenantSettings extends ISync {
|
export type TenantSetting = IWorkweekSettings | IGridSettings | ITimeFormatSettings | IViewSettings;
|
||||||
id: string;
|
|
||||||
lastModified?: string;
|
|
||||||
|
|
||||||
workweek: IWorkweekSettings;
|
/**
|
||||||
grid: IGridSettings;
|
* Settings IDs as const for type safety
|
||||||
timeFormat: ITimeFormatSettings;
|
*/
|
||||||
views: IViewSettings;
|
export const SettingsIds = {
|
||||||
}
|
WORKWEEK: 'workweek',
|
||||||
|
GRID: 'grid',
|
||||||
|
TIME_FORMAT: 'timeFormat',
|
||||||
|
VIEWS: 'views'
|
||||||
|
} as const;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
{
|
[
|
||||||
"id": "tenant-settings",
|
{
|
||||||
|
"id": "workweek",
|
||||||
"syncStatus": "synced",
|
"syncStatus": "synced",
|
||||||
"lastModified": "2025-12-15T10:00:00Z",
|
|
||||||
|
|
||||||
"workweek": {
|
|
||||||
"presets": {
|
"presets": {
|
||||||
"standard": {
|
"standard": {
|
||||||
"id": "standard",
|
"id": "standard",
|
||||||
|
|
@ -34,8 +32,9 @@
|
||||||
"defaultPreset": "standard",
|
"defaultPreset": "standard",
|
||||||
"firstDayOfWeek": 1
|
"firstDayOfWeek": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
"grid": {
|
"id": "grid",
|
||||||
|
"syncStatus": "synced",
|
||||||
"dayStartHour": 6,
|
"dayStartHour": 6,
|
||||||
"dayEndHour": 22,
|
"dayEndHour": 22,
|
||||||
"workStartHour": 8,
|
"workStartHour": 8,
|
||||||
|
|
@ -43,15 +42,17 @@
|
||||||
"hourHeight": 80,
|
"hourHeight": 80,
|
||||||
"snapInterval": 15
|
"snapInterval": 15
|
||||||
},
|
},
|
||||||
|
{
|
||||||
"timeFormat": {
|
"id": "timeFormat",
|
||||||
|
"syncStatus": "synced",
|
||||||
"timezone": "Europe/Copenhagen",
|
"timezone": "Europe/Copenhagen",
|
||||||
"locale": "da-DK",
|
"locale": "da-DK",
|
||||||
"use24HourFormat": true
|
"use24HourFormat": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
"views": {
|
"id": "views",
|
||||||
|
"syncStatus": "synced",
|
||||||
"availableViews": ["simple", "resource", "team", "department"],
|
"availableViews": ["simple", "resource", "team", "department"],
|
||||||
"defaultView": "simple"
|
"defaultView": "simple"
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue