feat: add Master Admin application functionality

This commit is contained in:
Gagan7900
2026-07-24 11:55:27 +05:30
parent 10038a52cf
commit d514d09879
224 changed files with 34594 additions and 958 deletions
@@ -0,0 +1,65 @@
import { DateTimeFormat } from '../models/date-time-format';
export const DATE_TIME_FORMATS: Readonly<Record<DateTimeFormat, Intl.DateTimeFormatOptions>> =
Object.freeze({
[DateTimeFormat.Date]: Object.freeze({
day: '2-digit',
month: 'short',
year: 'numeric'
}),
[DateTimeFormat.ShortDate]: Object.freeze({
day: '2-digit',
month: '2-digit',
year: 'numeric'
}),
[DateTimeFormat.LongDate]: Object.freeze({
weekday: 'long',
day: '2-digit',
month: 'long',
year: 'numeric'
}),
[DateTimeFormat.DateTime]: Object.freeze({
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true
}),
[DateTimeFormat.ShortDateTime]: Object.freeze({
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true
}),
[DateTimeFormat.LongDateTime]: Object.freeze({
weekday: 'long',
day: '2-digit',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true
}),
[DateTimeFormat.Time]: Object.freeze({
hour: '2-digit',
minute: '2-digit',
hour12: true
}),
[DateTimeFormat.TimeWithSeconds]: Object.freeze({
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true
}),
[DateTimeFormat.MonthYear]: Object.freeze({
month: 'long',
year: 'numeric'
}),
[DateTimeFormat.Year]: Object.freeze({
year: 'numeric'
})
});
@@ -0,0 +1,12 @@
export enum DateTimeFormat {
Date = 'date',
ShortDate = 'shortDate',
LongDate = 'longDate',
DateTime = 'dateTime',
ShortDateTime = 'shortDateTime',
LongDateTime = 'longDateTime',
Time = 'time',
TimeWithSeconds = 'timeWithSeconds',
MonthYear = 'monthYear',
Year = 'year'
}
@@ -0,0 +1,35 @@
import { DateTimeFormat } from '../models/date-time-format';
import { DateTimeFormatterService } from '../services/date-time-formatter.service';
import { AppDateTimePipe } from './app-date-time.pipe';
describe('AppDateTimePipe', () => {
let formatter: DateTimeFormatterService;
let pipe: AppDateTimePipe;
beforeEach(() => {
formatter = new DateTimeFormatterService();
pipe = new AppDateTimePipe(formatter);
});
it('matches the formatter service output', () => {
const value = '2026-07-20T14:30:45.000Z';
const expected = formatter.format(value, {
format: DateTimeFormat.LongDateTime,
timeZone: 'Asia/Dubai',
locale: 'en-GB',
fallback: 'N/A'
});
expect(pipe.transform(
value,
DateTimeFormat.LongDateTime,
'Asia/Dubai',
'en-GB',
'N/A'
)).toBe(expected);
});
it('passes a custom fallback through to the formatter', () => {
expect(pipe.transform(null, DateTimeFormat.Date, 'UTC', 'en-GB', 'N/A')).toBe('N/A');
});
});
@@ -0,0 +1,28 @@
import { Pipe, PipeTransform } from '@angular/core';
import { DateTimeFormat } from '../models/date-time-format';
import { DateTimeFormatterService } from '../services/date-time-formatter.service';
@Pipe({
name: 'appDateTime',
standalone: true,
pure: true
})
export class AppDateTimePipe implements PipeTransform {
constructor(private readonly dateTimeFormatter: DateTimeFormatterService) {}
transform(
value: string | number | Date | null | undefined,
format?: DateTimeFormat,
timeZone?: string,
locale?: string,
fallback?: string
): string {
return this.dateTimeFormatter.format(value, {
format,
timeZone,
locale,
fallback
});
}
}
@@ -0,0 +1,82 @@
import { DateTimeFormat } from '../models/date-time-format';
import { DateTimeFormatterService } from './date-time-formatter.service';
describe('DateTimeFormatterService', () => {
const isoDate = '2026-07-20T14:30:45.000Z';
const utcOptions = { timeZone: 'UTC' } as const;
let service: DateTimeFormatterService;
beforeEach(() => {
service = new DateTimeFormatterService();
});
it('formats an ISO UTC string using the default format', () => {
expect(service.format(isoDate, utcOptions)).toBe('20 Jul 2026, 02:30 pm');
});
it('formats a JavaScript Date', () => {
expect(service.format(new Date(isoDate), {
format: DateTimeFormat.Date,
timeZone: 'UTC'
})).toBe('20 Jul 2026');
});
it('formats a numeric timestamp', () => {
expect(service.format(Date.parse(isoDate), {
format: DateTimeFormat.TimeWithSeconds,
timeZone: 'UTC'
})).toBe('02:30:45 pm');
});
it.each([
[DateTimeFormat.Date, '20 Jul 2026'],
[DateTimeFormat.ShortDate, '20/07/2026'],
[DateTimeFormat.LongDate, 'Monday, 20 July 2026'],
[DateTimeFormat.DateTime, '20 Jul 2026, 02:30 pm'],
[DateTimeFormat.ShortDateTime, '20/07/2026, 02:30 pm'],
[DateTimeFormat.LongDateTime, 'Monday, 20 July 2026 at 02:30:45 pm'],
[DateTimeFormat.Time, '02:30 pm'],
[DateTimeFormat.TimeWithSeconds, '02:30:45 pm'],
[DateTimeFormat.MonthYear, 'July 2026'],
[DateTimeFormat.Year, '2026']
])('formats %s using its predefined options', (format, expected) => {
expect(service.format(isoDate, { format, timeZone: 'UTC' })).toBe(expected);
});
it.each([null, undefined, '', ' ', 'not-a-date'])(
'returns the default fallback for %s',
value => {
expect(service.format(value)).toBe('—');
}
);
it('uses a custom fallback', () => {
expect(service.format('invalid', { fallback: 'N/A' })).toBe('N/A');
});
it('supports a custom locale', () => {
expect(service.format(isoDate, {
format: DateTimeFormat.ShortDate,
locale: 'de-DE',
timeZone: 'UTC'
})).toBe('20.07.2026');
});
it('supports a valid IANA timezone', () => {
expect(service.format(isoDate, {
format: DateTimeFormat.Time,
timeZone: 'Asia/Dubai'
})).toBe('06:30 pm');
});
it('handles a day change caused by timezone conversion', () => {
expect(service.format('2026-07-20T23:30:00.000Z', {
format: DateTimeFormat.Date,
timeZone: 'Asia/Dubai'
})).toBe('21 Jul 2026');
});
it('returns the fallback instead of throwing for an invalid timezone', () => {
expect(service.format(isoDate, { timeZone: 'Invalid/Timezone' })).toBe('—');
});
});
@@ -0,0 +1,48 @@
import { Injectable } from '@angular/core';
import { DATE_TIME_FORMATS } from '../constants/date-time-formats';
import { DateTimeFormat } from '../models/date-time-format';
export interface DateTimeFormattingOptions {
readonly format?: DateTimeFormat;
readonly locale?: string;
readonly timeZone?: string;
readonly fallback?: string;
}
@Injectable({
providedIn: 'root'
})
export class DateTimeFormatterService {
format(
value: string | number | Date | null | undefined,
options: DateTimeFormattingOptions = {}
): string {
const fallback = options.fallback ?? '—';
if (value === null || value === undefined || (typeof value === 'string' && !value.trim())) {
return fallback;
}
const date = value instanceof Date
? new Date(value.getTime())
: new Date(value);
if (Number.isNaN(date.getTime())) {
return fallback;
}
const format = options.format ?? DateTimeFormat.DateTime;
const locale = options.locale?.trim() || 'en-GB';
const timeZone = options.timeZone?.trim();
try {
return new Intl.DateTimeFormat(locale, {
...DATE_TIME_FORMATS[format],
...(timeZone ? { timeZone } : {})
}).format(date);
} catch {
return fallback;
}
}
}