organization onboarding list, edit case changes
This commit is contained in:
@@ -54,7 +54,7 @@
|
|||||||
[columns]="columns()"
|
[columns]="columns()"
|
||||||
[rows]="tableStore.rows()"
|
[rows]="tableStore.rows()"
|
||||||
[actions]="actions()"
|
[actions]="actions()"
|
||||||
[totalRecords]="tableStore.totalRecords()"
|
[totalRecords]="tableStore.filteredRecords()"
|
||||||
[pageIndex]="tableStore.queryState.pageIndex()"
|
[pageIndex]="tableStore.queryState.pageIndex()"
|
||||||
[pageSize]="tableStore.queryState.pageSize()"
|
[pageSize]="tableStore.queryState.pageSize()"
|
||||||
tableTitle="Recently Created Organizations"
|
tableTitle="Recently Created Organizations"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Component, OnInit, inject, signal } from '@angular/core';
|
import { Component, OnInit, inject, signal } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { of } from 'rxjs';
|
|
||||||
|
|
||||||
import { DataTable } from '../../../shared/components/data-table/data-table';
|
import { DataTable } from '../../../shared/components/data-table/data-table';
|
||||||
import { DataTableStore } from '../../../shared/components/data-table/data-table.store';
|
import { DataTableStore } from '../../../shared/components/data-table/data-table.store';
|
||||||
@@ -9,12 +8,9 @@ import {
|
|||||||
DataTableAction,
|
DataTableAction,
|
||||||
DataTableActionEvent,
|
DataTableActionEvent,
|
||||||
DataTableColumn,
|
DataTableColumn,
|
||||||
DataTableRecord,
|
|
||||||
DataTableResult,
|
|
||||||
} from '../../../shared/components/data-table/data-table.types';
|
} from '../../../shared/components/data-table/data-table.types';
|
||||||
|
import { OrganizationDto, OrganizationStatus, OrganizationTableRow } from '../organization-list/models/organization.model';
|
||||||
type OrganizationPlan = 'Enterprise' | 'Standard' | 'Trial';
|
import { OrganizationService } from '../organization-list/data-access/organization.service';
|
||||||
type OrganizationStatus = 'Active' | 'Trial' | 'Suspended';
|
|
||||||
|
|
||||||
interface DashboardStatCard {
|
interface DashboardStatCard {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -25,17 +21,6 @@ interface DashboardStatCard {
|
|||||||
iconBackgroundClass: string;
|
iconBackgroundClass: string;
|
||||||
helperClass: string;
|
helperClass: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OrganizationListRow extends DataTableRecord {
|
|
||||||
id: string;
|
|
||||||
code: string;
|
|
||||||
organizationName: string;
|
|
||||||
countryId: string;
|
|
||||||
country: string;
|
|
||||||
plan: string;
|
|
||||||
status: OrganizationStatus;
|
|
||||||
expiry: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dashboard',
|
selector: 'dashboard',
|
||||||
@@ -48,7 +33,8 @@ interface OrganizationListRow extends DataTableRecord {
|
|||||||
export class Dashboard implements OnInit {
|
export class Dashboard implements OnInit {
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly toastr = inject(ToastrService);
|
private readonly toastr = inject(ToastrService);
|
||||||
readonly tableStore = inject(DataTableStore<OrganizationListRow, OrganizationListRow>);
|
private readonly organizationApi = inject(OrganizationService);
|
||||||
|
readonly tableStore = inject(DataTableStore<OrganizationDto, OrganizationTableRow>);
|
||||||
|
|
||||||
readonly statCards = signal<DashboardStatCard[]>([
|
readonly statCards = signal<DashboardStatCard[]>([
|
||||||
{
|
{
|
||||||
@@ -107,100 +93,8 @@ export class Dashboard implements OnInit {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
readonly recentOrganizations = signal<OrganizationListRow[]>([
|
readonly columns = signal<DataTableColumn<OrganizationTableRow>[]>([
|
||||||
{
|
{ key: 'serialNumber', label: 'Sr. No.', header: 'Sr.No.', sortable: false, width: '90px' },
|
||||||
id: '1',
|
|
||||||
code: 'ORG-0024',
|
|
||||||
organizationName: 'Syscom Group',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Enterprise',
|
|
||||||
status: 'Active',
|
|
||||||
expiry: '31-12-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
code: 'ORG-0023',
|
|
||||||
organizationName: 'Acme Trading',
|
|
||||||
countryId: 'c2',
|
|
||||||
country: 'India',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Active',
|
|
||||||
expiry: '31-03-2027'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
code: 'ORG-0022',
|
|
||||||
organizationName: 'Falcon Retail LLC',
|
|
||||||
countryId: 'c3',
|
|
||||||
country: 'UAE',
|
|
||||||
plan: 'Trial',
|
|
||||||
status: 'Trial',
|
|
||||||
expiry: '28-07-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
code: 'ORG-0021',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '5',
|
|
||||||
code: 'ORG-0022',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '6',
|
|
||||||
code: 'ORG-0023',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '7',
|
|
||||||
code: 'ORG-0024',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '8',
|
|
||||||
code: 'ORG-0025',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '9',
|
|
||||||
code: 'ORG-0026',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
readonly columns = signal<DataTableColumn<OrganizationListRow>[]>([
|
|
||||||
{ key: 'code', label: 'Code', header: 'Code', sortable: true },
|
{ key: 'code', label: 'Code', header: 'Code', sortable: true },
|
||||||
{
|
{
|
||||||
key: 'organizationName',
|
key: 'organizationName',
|
||||||
@@ -217,7 +111,7 @@ export class Dashboard implements OnInit {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
badge: true,
|
badge: true,
|
||||||
badgeClass: value =>
|
badgeClass: value =>
|
||||||
value === 'Trial'
|
value === 'Trial' || value === OrganizationStatus.Trial
|
||||||
? 'badge bg-warning/10 text-warning'
|
? 'badge bg-warning/10 text-warning'
|
||||||
: 'badge bg-light text-defaulttextcolor',
|
: 'badge bg-light text-defaulttextcolor',
|
||||||
},
|
},
|
||||||
@@ -228,9 +122,9 @@ export class Dashboard implements OnInit {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
badge: true,
|
badge: true,
|
||||||
badgeClass: value =>
|
badgeClass: value =>
|
||||||
value === 'Active'
|
value === 'Active' || value === OrganizationStatus.Active
|
||||||
? 'badge bg-success/10 text-success'
|
? 'badge bg-success/10 text-success'
|
||||||
: value === 'Trial'
|
: value === 'Trial' || value === OrganizationStatus.Trial
|
||||||
? 'badge bg-warning/10 text-warning'
|
? 'badge bg-warning/10 text-warning'
|
||||||
: 'badge bg-danger/10 text-danger',
|
: 'badge bg-danger/10 text-danger',
|
||||||
},
|
},
|
||||||
@@ -240,7 +134,7 @@ export class Dashboard implements OnInit {
|
|||||||
readonly emptyMessage = signal<string>('No Organizations');
|
readonly emptyMessage = signal<string>('No Organizations');
|
||||||
readonly emptyDescription = signal<string>('Start by adding your first organization');
|
readonly emptyDescription = signal<string>('Start by adding your first organization');
|
||||||
|
|
||||||
readonly actions = signal<DataTableAction<OrganizationListRow>[]>([
|
readonly actions = signal<DataTableAction<OrganizationTableRow>[]>([
|
||||||
{ type: 'view', label: 'View', icon: 'ti ti-eye', className: 'text-info' },
|
{ type: 'view', label: 'View', icon: 'ti ti-eye', className: 'text-info' },
|
||||||
{ type: 'edit', label: 'Edit', icon: 'ti ti-edit', className: 'text-primary' },
|
{ type: 'edit', label: 'Edit', icon: 'ti ti-edit', className: 'text-primary' },
|
||||||
{
|
{
|
||||||
@@ -248,47 +142,45 @@ export class Dashboard implements OnInit {
|
|||||||
label: 'Suspend',
|
label: 'Suspend',
|
||||||
icon: 'ti ti-player-pause',
|
icon: 'ti ti-player-pause',
|
||||||
className: 'text-warning',
|
className: 'text-warning',
|
||||||
visible: row => row.status !== 'Suspended',
|
visible: row => row.status !== 'Suspended' && row.status !== OrganizationStatus.Suspended,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.tableStore.initialize({
|
this.tableStore.initialize({
|
||||||
fetcher: query => {
|
fetcher: query => this.organizationApi.getOrganizationDataTable(query),
|
||||||
const search = (query.search || '').trim().toLowerCase();
|
mapRow: (item, serialNumber) => {
|
||||||
const sortBy = query.sortBy;
|
const statusText = typeof item.status === 'number'
|
||||||
const sortDir = query.sortDir;
|
? (OrganizationStatus[item.status] ?? 'Active')
|
||||||
|
: (item.status || 'Active');
|
||||||
|
|
||||||
let rows = this.recentOrganizations().filter(row => {
|
const resolvedName = item.name || item.organizationName || '—';
|
||||||
const matchesSearch = !search
|
const resolvedCountry = item.countryName || item.country || '—';
|
||||||
|| row.code.toLowerCase().includes(search)
|
const resolvedPlan = item.planName || item.plan || '—';
|
||||||
|| row.organizationName.toLowerCase().includes(search)
|
const resolvedExpiry = item.expiryDate || item.expiry || '—';
|
||||||
|| row.country.toLowerCase().includes(search)
|
|
||||||
|| row.plan.toLowerCase().includes(search)
|
|
||||||
|| row.status.toLowerCase().includes(search)
|
|
||||||
|| (row.expiry && row.expiry.toLowerCase().includes(search));
|
|
||||||
return matchesSearch;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sortBy) {
|
return {
|
||||||
rows = [...rows].sort((left, right) => {
|
...item,
|
||||||
const leftVal = String(left[sortBy as keyof OrganizationListRow] ?? '').toLowerCase();
|
id: item.id,
|
||||||
const rightVal = String(right[sortBy as keyof OrganizationListRow] ?? '').toLowerCase();
|
code: item.code || '—',
|
||||||
const compared = leftVal.localeCompare(rightVal);
|
name: resolvedName,
|
||||||
return sortDir === 'asc' ? compared : -compared;
|
organizationName: resolvedName,
|
||||||
});
|
countryId: item.countryId || null,
|
||||||
}
|
country: resolvedCountry,
|
||||||
|
countryName: resolvedCountry,
|
||||||
const total = rows.length;
|
plan: resolvedPlan,
|
||||||
const start = (query.page - 1) * query.pageSize;
|
planName: resolvedPlan,
|
||||||
const end = start + query.pageSize;
|
status: statusText,
|
||||||
const result: DataTableResult<OrganizationListRow> = {
|
expiry: resolvedExpiry,
|
||||||
draw: query.draw,
|
expiryDate: resolvedExpiry,
|
||||||
total,
|
dataRegion: item.dataRegion || null,
|
||||||
filtered: total,
|
isActive: item.isActive ?? true,
|
||||||
rows: rows.slice(start, end)
|
serialNumber,
|
||||||
};
|
};
|
||||||
return of(result);
|
},
|
||||||
|
onError: (err: any) => {
|
||||||
|
const msg = err?.error?.message || err?.error?.title || 'Failed to load dashboard organizations.';
|
||||||
|
this.toastr.error(msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -297,7 +189,7 @@ export class Dashboard implements OnInit {
|
|||||||
void this.router.navigate(['/organizations/onboarding']);
|
void this.router.navigate(['/organizations/onboarding']);
|
||||||
}
|
}
|
||||||
|
|
||||||
onActionClick(event: DataTableActionEvent<OrganizationListRow>): void {
|
onActionClick(event: DataTableActionEvent<OrganizationTableRow>): void {
|
||||||
if (event.action.type === 'edit' || (event.row.status as string) === 'Draft') {
|
if (event.action.type === 'edit' || (event.row.status as string) === 'Draft') {
|
||||||
void this.router.navigate(['/organizations/onboarding'], { queryParams: { id: event.row.id } });
|
void this.router.navigate(['/organizations/onboarding'], { queryParams: { id: event.row.id } });
|
||||||
return;
|
return;
|
||||||
@@ -306,3 +198,4 @@ export class Dashboard implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
import { buildApiUrl } from '../../../../core/config/api-url.util';
|
||||||
|
|
||||||
|
export const ORGANIZATION_ENDPOINTS = {
|
||||||
|
dataTable: buildApiUrl(
|
||||||
|
'masterAdmin',
|
||||||
|
'/v1/tenants/datatable'
|
||||||
|
),
|
||||||
|
|
||||||
|
getById: (id: string) =>
|
||||||
|
buildApiUrl(
|
||||||
|
'masterAdmin',
|
||||||
|
`/v1/tenants/${encodeURIComponent(id)}`
|
||||||
|
),
|
||||||
|
|
||||||
|
autocomplete: buildApiUrl(
|
||||||
|
'masterAdmin',
|
||||||
|
'/v1/tenants/autocomplete'
|
||||||
|
),
|
||||||
|
} as const;
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||||
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { ORGANIZATION_ENDPOINTS } from './organization.endpoints';
|
||||||
|
import { DataTableQuery, DataTableResult } from '../../../../shared/components/data-table/data-table.types';
|
||||||
|
import {
|
||||||
|
OrganizationDto,
|
||||||
|
OrganizationLookupDto,
|
||||||
|
OrganizationTableRow
|
||||||
|
} from '../models/organization.model';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class OrganizationService {
|
||||||
|
private readonly http = inject(HttpClient);
|
||||||
|
|
||||||
|
getOrganizationDataTable(query: DataTableQuery, countryId?: string | null): Observable<DataTableResult<OrganizationDto>> {
|
||||||
|
const payload = {
|
||||||
|
...query,
|
||||||
|
...(countryId ? { countryId } : {})
|
||||||
|
};
|
||||||
|
return this.http.post<DataTableResult<OrganizationDto>>(ORGANIZATION_ENDPOINTS.dataTable, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizationById(id: string): Observable<OrganizationDto> {
|
||||||
|
return this.http.get<OrganizationDto>(ORGANIZATION_ENDPOINTS.getById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
autocomplete(term?: string, limit = 10): Observable<readonly OrganizationLookupDto[]> {
|
||||||
|
let params = new HttpParams().set('limit', limit);
|
||||||
|
const normalizedTerm = term?.trim();
|
||||||
|
|
||||||
|
if (normalizedTerm) {
|
||||||
|
params = params.set('term', normalizedTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.get<readonly OrganizationLookupDto[]>(ORGANIZATION_ENDPOINTS.autocomplete, { params });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { DataTableRecord } from '../../../../shared/components/data-table/data-table.types';
|
||||||
|
|
||||||
|
export enum OrganizationStatus { Trial = 0, Active = 1, Suspended = 2, Cancelled = 3 }
|
||||||
|
|
||||||
|
export interface OrganizationDto {
|
||||||
|
id: string;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
organizationName?: string | null;
|
||||||
|
status: OrganizationStatus | string;
|
||||||
|
countryId?: string | null;
|
||||||
|
countryName?: string | null;
|
||||||
|
country?: string | null;
|
||||||
|
planId?: string | null;
|
||||||
|
planName?: string | null;
|
||||||
|
plan?: string | null;
|
||||||
|
expiryDate?: string | null;
|
||||||
|
expiry?: string | null;
|
||||||
|
defaultLanguageId?: string | null;
|
||||||
|
defaultLanguageName?: string | null;
|
||||||
|
defaultDbConnectionId?: string | null;
|
||||||
|
defaultDbConnectionName?: string | null;
|
||||||
|
defaultCurrencyId?: string | null;
|
||||||
|
defaultCurrencyName?: string | null;
|
||||||
|
defaultTimezoneId?: string | null;
|
||||||
|
defaultTimezoneName?: string | null;
|
||||||
|
dataRegion?: string | null;
|
||||||
|
isActive?: boolean;
|
||||||
|
createdOn?: string;
|
||||||
|
modifiedOn?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrganizationLookupDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrganizationTableRow extends DataTableRecord {
|
||||||
|
readonly id: string;
|
||||||
|
readonly code: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly organizationName: string;
|
||||||
|
readonly countryId: string | null;
|
||||||
|
readonly country: string;
|
||||||
|
readonly countryName: string | null;
|
||||||
|
readonly plan: string;
|
||||||
|
readonly planName: string | null;
|
||||||
|
readonly status: string | OrganizationStatus;
|
||||||
|
readonly expiry: string;
|
||||||
|
readonly expiryDate: string | null;
|
||||||
|
readonly dataRegion: string | null;
|
||||||
|
readonly isActive: boolean;
|
||||||
|
readonly serialNumber: number;
|
||||||
|
readonly defaultLanguageId?: string | null;
|
||||||
|
readonly defaultCurrencyId?: string | null;
|
||||||
|
readonly defaultTimezoneId?: string | null;
|
||||||
|
readonly defaultDbConnectionId?: string | null;
|
||||||
|
readonly createdOn?: string;
|
||||||
|
readonly modifiedOn?: string | null;
|
||||||
|
readonly defaultLanguageName?: string | null;
|
||||||
|
readonly defaultCurrencyName?: string | null;
|
||||||
|
readonly defaultTimezoneName?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OrganizationModalMode = 'create' | 'edit' | 'view';
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
[columns]="columns()"
|
[columns]="columns()"
|
||||||
[rows]="tableStore.rows()"
|
[rows]="tableStore.rows()"
|
||||||
[actions]="actions()"
|
[actions]="actions()"
|
||||||
[totalRecords]="tableStore.totalRecords()"
|
[totalRecords]="tableStore.filteredRecords()"
|
||||||
[pageIndex]="tableStore.queryState.pageIndex()"
|
[pageIndex]="tableStore.queryState.pageIndex()"
|
||||||
[pageSize]="tableStore.queryState.pageSize()"
|
[pageSize]="tableStore.queryState.pageSize()"
|
||||||
tableTitle="Organizations"
|
tableTitle="Organizations"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Component, OnInit, inject, signal } from '@angular/core';
|
|||||||
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { of } from 'rxjs';
|
|
||||||
|
|
||||||
import { DataTable, DataTableToolbarDirective } from '../../../shared/components/data-table/data-table';
|
import { DataTable, DataTableToolbarDirective } from '../../../shared/components/data-table/data-table';
|
||||||
import { DataTableStore } from '../../../shared/components/data-table/data-table.store';
|
import { DataTableStore } from '../../../shared/components/data-table/data-table.store';
|
||||||
@@ -10,31 +9,17 @@ import {
|
|||||||
DataTableAction,
|
DataTableAction,
|
||||||
DataTableActionEvent,
|
DataTableActionEvent,
|
||||||
DataTableColumn,
|
DataTableColumn,
|
||||||
DataTableRecord,
|
|
||||||
DataTableResult
|
|
||||||
} from '../../../shared/components/data-table/data-table.types';
|
} from '../../../shared/components/data-table/data-table.types';
|
||||||
import { Autocomplete } from '../../../shared/components/form/autocomplete/autocomplete';
|
import { Autocomplete } from '../../../shared/components/form/autocomplete/autocomplete';
|
||||||
import {
|
import {
|
||||||
AutocompleteDisplayFn,
|
AutocompleteDisplayFn,
|
||||||
AutocompleteResolveValueFn,
|
|
||||||
AutocompleteSearchFn,
|
AutocompleteSearchFn,
|
||||||
AutocompleteValueFn,
|
AutocompleteValueFn,
|
||||||
} from '../../../shared/components/form/autocomplete/autocomplete.types';
|
} from '../../../shared/components/form/autocomplete/autocomplete.types';
|
||||||
import { Button } from '../../../shared/components/button/button';
|
import { Button } from '../../../shared/components/button/button';
|
||||||
import { CountryLookupDto, CountryService } from '../../global-masters/countries/public-api';
|
import { CountryLookupDto, CountryService } from '../../global-masters/countries/public-api';
|
||||||
|
import { OrganizationDto, OrganizationStatus, OrganizationTableRow } from './models/organization.model';
|
||||||
type OrganizationStatus = 'Active' | 'Trial' | 'Suspended';
|
import { OrganizationService } from './data-access/organization.service';
|
||||||
|
|
||||||
interface OrganizationListRow extends DataTableRecord {
|
|
||||||
id: string;
|
|
||||||
code: string;
|
|
||||||
organizationName: string;
|
|
||||||
countryId: string;
|
|
||||||
country: string;
|
|
||||||
plan: string;
|
|
||||||
status: OrganizationStatus;
|
|
||||||
expiry: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'organization-list',
|
selector: 'organization-list',
|
||||||
@@ -49,7 +34,8 @@ export class OrganizationList implements OnInit {
|
|||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly toastr = inject(ToastrService);
|
private readonly toastr = inject(ToastrService);
|
||||||
private readonly countryApi = inject(CountryService);
|
private readonly countryApi = inject(CountryService);
|
||||||
readonly tableStore = inject(DataTableStore<OrganizationListRow, OrganizationListRow>);
|
private readonly organizationApi = inject(OrganizationService);
|
||||||
|
readonly tableStore = inject(DataTableStore<OrganizationDto, OrganizationTableRow>);
|
||||||
|
|
||||||
readonly selectedCountryLookup = signal<CountryLookupDto | null>(null);
|
readonly selectedCountryLookup = signal<CountryLookupDto | null>(null);
|
||||||
readonly appliedCountryId = signal<string | null>(null);
|
readonly appliedCountryId = signal<string | null>(null);
|
||||||
@@ -59,104 +45,11 @@ export class OrganizationList implements OnInit {
|
|||||||
countryId: [''],
|
countryId: [''],
|
||||||
});
|
});
|
||||||
|
|
||||||
readonly allOrganizations = signal<OrganizationListRow[]>([
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
code: 'ORG-0024',
|
|
||||||
organizationName: 'Syscom Group',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Enterprise',
|
|
||||||
status: 'Active',
|
|
||||||
expiry: '31-12-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
code: 'ORG-0023',
|
|
||||||
organizationName: 'Acme Trading',
|
|
||||||
countryId: 'c2',
|
|
||||||
country: 'India',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Active',
|
|
||||||
expiry: '31-03-2027'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
code: 'ORG-0022',
|
|
||||||
organizationName: 'Falcon Retail LLC',
|
|
||||||
countryId: 'c3',
|
|
||||||
country: 'UAE',
|
|
||||||
plan: 'Trial',
|
|
||||||
status: 'Trial',
|
|
||||||
expiry: '28-07-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
code: 'ORG-0021',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '5',
|
|
||||||
code: 'ORG-0022',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '6',
|
|
||||||
code: 'ORG-0023',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '7',
|
|
||||||
code: 'ORG-0024',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '8',
|
|
||||||
code: 'ORG-0025',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '9',
|
|
||||||
code: 'ORG-0026',
|
|
||||||
organizationName: 'Oasis Foods',
|
|
||||||
countryId: 'c1',
|
|
||||||
country: 'Saudi Arabia',
|
|
||||||
plan: 'Standard',
|
|
||||||
status: 'Suspended',
|
|
||||||
expiry: '15-06-2026'
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
readonly searchCountries: AutocompleteSearchFn<CountryLookupDto> = (term, limit) => this.countryApi.autocomplete(term, limit);
|
readonly searchCountries: AutocompleteSearchFn<CountryLookupDto> = (term, limit) => this.countryApi.autocomplete(term, limit);
|
||||||
readonly displayCountry: AutocompleteDisplayFn<CountryLookupDto> = country => country.name;
|
readonly displayCountry: AutocompleteDisplayFn<CountryLookupDto> = country => country.name;
|
||||||
readonly countryValue: AutocompleteValueFn<CountryLookupDto, string> = country => country.id;
|
readonly countryValue: AutocompleteValueFn<CountryLookupDto, string> = country => country.id;
|
||||||
|
|
||||||
readonly columns = signal<DataTableColumn<OrganizationListRow>[]>([
|
readonly columns = signal<DataTableColumn<OrganizationTableRow>[]>([
|
||||||
{ key: 'code', label: 'Code', header: 'Code', sortable: true },
|
{ key: 'code', label: 'Code', header: 'Code', sortable: true },
|
||||||
{
|
{
|
||||||
key: 'organizationName',
|
key: 'organizationName',
|
||||||
@@ -173,7 +66,7 @@ export class OrganizationList implements OnInit {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
badge: true,
|
badge: true,
|
||||||
badgeClass: value =>
|
badgeClass: value =>
|
||||||
value === 'Trial'
|
value === 'Trial' || value === OrganizationStatus.Trial
|
||||||
? 'badge bg-warning/10 text-warning'
|
? 'badge bg-warning/10 text-warning'
|
||||||
: 'badge bg-light text-defaulttextcolor',
|
: 'badge bg-light text-defaulttextcolor',
|
||||||
},
|
},
|
||||||
@@ -184,15 +77,15 @@ export class OrganizationList implements OnInit {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
badge: true,
|
badge: true,
|
||||||
badgeClass: value => {
|
badgeClass: value => {
|
||||||
if (value === 'Active') return 'badge bg-success/10 text-success';
|
if (value === 'Active' || value === OrganizationStatus.Active) return 'badge bg-success/10 text-success';
|
||||||
if (value === 'Trial') return 'badge bg-warning/10 text-warning';
|
if (value === 'Trial' || value === OrganizationStatus.Trial) return 'badge bg-warning/10 text-warning';
|
||||||
return 'badge bg-danger/10 text-danger';
|
return 'badge bg-danger/10 text-danger';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ key: 'expiry', label: 'Expiry', header: 'Expiry', sortable: true },
|
{ key: 'expiry', label: 'Expiry', header: 'Expiry', sortable: true },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
readonly actions = signal<DataTableAction<OrganizationListRow>[]>([
|
readonly actions = signal<DataTableAction<OrganizationTableRow>[]>([
|
||||||
{ type: 'view', label: 'View', icon: 'ti ti-eye', className: 'text-info' },
|
{ type: 'view', label: 'View', icon: 'ti ti-eye', className: 'text-info' },
|
||||||
{ type: 'edit', label: 'Edit', icon: 'ti ti-edit', className: 'text-primary' },
|
{ type: 'edit', label: 'Edit', icon: 'ti ti-edit', className: 'text-primary' },
|
||||||
{
|
{
|
||||||
@@ -200,52 +93,45 @@ export class OrganizationList implements OnInit {
|
|||||||
label: 'Suspend',
|
label: 'Suspend',
|
||||||
icon: 'ti ti-player-pause',
|
icon: 'ti ti-player-pause',
|
||||||
className: 'text-warning',
|
className: 'text-warning',
|
||||||
visible: row => row.status !== 'Suspended',
|
visible: row => row.status !== 'Suspended' && row.status !== OrganizationStatus.Suspended,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.tableStore.initialize({
|
this.tableStore.initialize({
|
||||||
fetcher: query => {
|
fetcher: query => this.organizationApi.getOrganizationDataTable(query, this.appliedCountryId()),
|
||||||
const countryId = this.appliedCountryId();
|
mapRow: (item, serialNumber) => {
|
||||||
const selectedCountryName = this.selectedCountryLookup()?.name?.toLowerCase();
|
const statusText = typeof item.status === 'number'
|
||||||
const search = (query.search || '').trim().toLowerCase();
|
? (OrganizationStatus[item.status] ?? 'Active')
|
||||||
const sortBy = query.sortBy;
|
: (item.status || 'Active');
|
||||||
const sortDir = query.sortDir;
|
|
||||||
|
|
||||||
let rows = this.allOrganizations().filter(row => {
|
const resolvedName = item.name || item.organizationName || '—';
|
||||||
const matchesCountry = !countryId
|
const resolvedCountry = item.countryName || item.country || '—';
|
||||||
|| row.countryId === countryId
|
const resolvedPlan = item.planName || item.plan || '—';
|
||||||
|| (!!selectedCountryName && row.country.toLowerCase() === selectedCountryName);
|
const resolvedExpiry = item.expiryDate || item.expiry || '—';
|
||||||
const matchesSearch = !search
|
|
||||||
|| row.code.toLowerCase().includes(search)
|
|
||||||
|| row.organizationName.toLowerCase().includes(search)
|
|
||||||
|| row.country.toLowerCase().includes(search)
|
|
||||||
|| row.plan.toLowerCase().includes(search)
|
|
||||||
|| row.status.toLowerCase().includes(search)
|
|
||||||
|| row.expiry.toLowerCase().includes(search);
|
|
||||||
return matchesCountry && matchesSearch;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sortBy) {
|
return {
|
||||||
rows = [...rows].sort((left, right) => {
|
...item,
|
||||||
const leftVal = String(left[sortBy as keyof OrganizationListRow] ?? '').toLowerCase();
|
id: item.id,
|
||||||
const rightVal = String(right[sortBy as keyof OrganizationListRow] ?? '').toLowerCase();
|
code: item.code || '—',
|
||||||
const compared = leftVal.localeCompare(rightVal);
|
name: resolvedName,
|
||||||
return sortDir === 'asc' ? compared : -compared;
|
organizationName: resolvedName,
|
||||||
});
|
countryId: item.countryId || null,
|
||||||
}
|
country: resolvedCountry,
|
||||||
|
countryName: resolvedCountry,
|
||||||
const total = rows.length;
|
plan: resolvedPlan,
|
||||||
const start = (query.page - 1) * query.pageSize;
|
planName: resolvedPlan,
|
||||||
const end = start + query.pageSize;
|
status: statusText,
|
||||||
const result: DataTableResult<OrganizationListRow> = {
|
expiry: resolvedExpiry,
|
||||||
draw: query.draw,
|
expiryDate: resolvedExpiry,
|
||||||
total,
|
dataRegion: item.dataRegion || null,
|
||||||
filtered: total,
|
isActive: item.isActive ?? true,
|
||||||
rows: rows.slice(start, end)
|
serialNumber,
|
||||||
};
|
};
|
||||||
return of(result);
|
},
|
||||||
|
onError: (err: any) => {
|
||||||
|
const msg = err?.error?.message || err?.error?.title || 'Failed to load organization list.';
|
||||||
|
this.toastr.error(msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -279,7 +165,7 @@ export class OrganizationList implements OnInit {
|
|||||||
this.showFilters.update(value => !value);
|
this.showFilters.update(value => !value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onActionClick(event: DataTableActionEvent<OrganizationListRow>): void {
|
onActionClick(event: DataTableActionEvent<OrganizationTableRow>): void {
|
||||||
if (event.action.type === 'edit' || (event.row.status as string) === 'Draft') {
|
if (event.action.type === 'edit' || (event.row.status as string) === 'Draft') {
|
||||||
void this.router.navigate(['/organizations/onboarding'], { queryParams: { id: event.row.id } });
|
void this.router.navigate(['/organizations/onboarding'], { queryParams: { id: event.row.id } });
|
||||||
return;
|
return;
|
||||||
@@ -288,3 +174,4 @@ export class OrganizationList implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+20
@@ -110,6 +110,25 @@
|
|||||||
|
|
||||||
<!-- Button Group Footer -->
|
<!-- Button Group Footer -->
|
||||||
<footer class="modern-modal-footer mt-5 px-2 sm:px-6">
|
<footer class="modern-modal-footer mt-5 px-2 sm:px-6">
|
||||||
|
@if (isEditMode()) {
|
||||||
|
<div class="flex flex-col-reverse gap-2.5 sm:flex-row sm:items-center sm:justify-end sm:gap-3">
|
||||||
|
<!-- Cancel -->
|
||||||
|
<div class="w-full sm:w-auto">
|
||||||
|
<app-button action="cancel" [variant]="'custom'" label="Cancel" [showIcon]="true"
|
||||||
|
[disabled]="navigationDisabled()" [fullWidth]="true"
|
||||||
|
className="onboarding-nav-btn onboarding-save-btn ti-btn-outline-primary whitespace-nowrap justify-center sm:w-32"
|
||||||
|
(buttonClicked)="requestCancel()"></app-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Save Changes -->
|
||||||
|
<div class="w-full sm:w-auto">
|
||||||
|
<app-button action="update" label="Update" loadingLabel="Updating..." [showIcon]="true"
|
||||||
|
[loading]="finishing() || savingDraft()" [disabled]="navigationDisabled() || savingDraft() || finishing()"
|
||||||
|
[fullWidth]="true" className="onboarding-nav-btn justify-center sm:w-44"
|
||||||
|
(buttonClicked)="requestSaveChanges()"></app-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
<div class="flex flex-col-reverse gap-2.5 sm:flex-row sm:items-center sm:justify-end sm:gap-3">
|
<div class="flex flex-col-reverse gap-2.5 sm:flex-row sm:items-center sm:justify-end sm:gap-3">
|
||||||
<!-- Secondary Actions: Cancel/Back & Save Draft (Left group on Desktop, Bottom row on Mobile) -->
|
<!-- Secondary Actions: Cancel/Back & Save Draft (Left group on Desktop, Bottom row on Mobile) -->
|
||||||
<div class="grid grid-cols-2 gap-2.5 w-full sm:flex sm:w-auto sm:items-center sm:gap-3">
|
<div class="grid grid-cols-2 gap-2.5 w-full sm:flex sm:w-auto sm:items-center sm:gap-3">
|
||||||
@@ -153,6 +172,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</footer>
|
</footer>
|
||||||
<!-- End Button Group Footer -->
|
<!-- End Button Group Footer -->
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+11
@@ -24,6 +24,8 @@ export class OnboardingStepper {
|
|||||||
readonly currentStepIndex = input.required<number>();
|
readonly currentStepIndex = input.required<number>();
|
||||||
readonly completedStepIndexes = input<readonly number[]>([]);
|
readonly completedStepIndexes = input<readonly number[]>([]);
|
||||||
|
|
||||||
|
readonly isEditMode = input(false);
|
||||||
|
|
||||||
readonly savingDraft = input(false);
|
readonly savingDraft = input(false);
|
||||||
readonly finishing = input(false);
|
readonly finishing = input(false);
|
||||||
readonly navigationDisabled = input(false);
|
readonly navigationDisabled = input(false);
|
||||||
@@ -32,6 +34,7 @@ export class OnboardingStepper {
|
|||||||
readonly backClicked = output<void>();
|
readonly backClicked = output<void>();
|
||||||
readonly nextClicked = output<void>();
|
readonly nextClicked = output<void>();
|
||||||
readonly saveDraftClicked = output<void>();
|
readonly saveDraftClicked = output<void>();
|
||||||
|
readonly saveChangesClicked = output<void>();
|
||||||
readonly finishClicked = output<void>();
|
readonly finishClicked = output<void>();
|
||||||
readonly cancelClicked = output<void>();
|
readonly cancelClicked = output<void>();
|
||||||
|
|
||||||
@@ -122,6 +125,14 @@ export class OnboardingStepper {
|
|||||||
this.saveDraftClicked.emit();
|
this.saveDraftClicked.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestSaveChanges(): void {
|
||||||
|
if (this.navigationDisabled() || this.finishing() || this.savingDraft()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveChangesClicked.emit();
|
||||||
|
}
|
||||||
|
|
||||||
requestFinish(): void {
|
requestFinish(): void {
|
||||||
if (
|
if (
|
||||||
this.navigationDisabled() ||
|
this.navigationDisabled() ||
|
||||||
|
|||||||
+61
-14
@@ -35,6 +35,7 @@ const INITIAL_DRAFT_STATE: OnboardingDraftState = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrganizationOnboardingStateService {
|
export class OrganizationOnboardingStateService {
|
||||||
|
private readonly isEditModeState = signal(false);
|
||||||
private readonly organizationIdState = signal<string | null>(null);
|
private readonly organizationIdState = signal<string | null>(null);
|
||||||
private readonly onboardingDataState = signal<OrganizationOnboardingData>(INITIAL_DATA);
|
private readonly onboardingDataState = signal<OrganizationOnboardingData>(INITIAL_DATA);
|
||||||
private readonly onboardingDraftState = signal<OnboardingDraftState>(INITIAL_DRAFT_STATE);
|
private readonly onboardingDraftState = signal<OnboardingDraftState>(INITIAL_DRAFT_STATE);
|
||||||
@@ -43,6 +44,7 @@ export class OrganizationOnboardingStateService {
|
|||||||
private readonly savedDraftIdState = signal<string | null>(null);
|
private readonly savedDraftIdState = signal<string | null>(null);
|
||||||
private readonly savedAtState = signal<string | null>(null);
|
private readonly savedAtState = signal<string | null>(null);
|
||||||
|
|
||||||
|
readonly isEditMode = this.isEditModeState.asReadonly();
|
||||||
readonly organizationId = this.organizationIdState.asReadonly();
|
readonly organizationId = this.organizationIdState.asReadonly();
|
||||||
readonly onboardingData = this.onboardingDataState.asReadonly();
|
readonly onboardingData = this.onboardingDataState.asReadonly();
|
||||||
readonly onboardingDraft = this.onboardingDraftState.asReadonly();
|
readonly onboardingDraft = this.onboardingDraftState.asReadonly();
|
||||||
@@ -60,6 +62,10 @@ export class OrganizationOnboardingStateService {
|
|||||||
return !!(data.basics || data.localization || data.planLimits || data.admin);
|
return !!(data.basics || data.localization || data.planLimits || data.admin);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setIsEditMode(isEdit: boolean): void {
|
||||||
|
this.isEditModeState.set(isEdit);
|
||||||
|
}
|
||||||
|
|
||||||
setOrganizationId(id: string | null): void {
|
setOrganizationId(id: string | null): void {
|
||||||
this.organizationIdState.set(id);
|
this.organizationIdState.set(id);
|
||||||
}
|
}
|
||||||
@@ -164,28 +170,68 @@ export class OrganizationOnboardingStateService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreServerDraft(serverDraft: OrganizationServerDraftResponse): void {
|
restoreServerDraft(serverDraft: OrganizationServerDraftResponse, isEditMode = false): void {
|
||||||
this.organizationIdState.set(serverDraft.id);
|
this.organizationIdState.set(serverDraft.id);
|
||||||
|
this.isEditModeState.set(isEditMode);
|
||||||
|
|
||||||
|
const rawRecord = serverDraft as Record<string, any>;
|
||||||
|
const orgCode = serverDraft.code ?? rawRecord['Code'] ?? rawRecord['code'] ?? rawRecord['organizationCode'] ?? rawRecord['OrganizationCode'] ?? null;
|
||||||
|
|
||||||
|
let basicsData = serverDraft.basics ?? rawRecord['Basics'] ?? rawRecord['basics'] ?? (
|
||||||
|
rawRecord['name'] || rawRecord['Name'] || rawRecord['organizationName'] || rawRecord['OrganizationName'] || orgCode
|
||||||
|
? rawRecord
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (basicsData) {
|
||||||
|
basicsData = {
|
||||||
|
...basicsData,
|
||||||
|
code: basicsData['code'] ?? basicsData['Code'] ?? basicsData['organizationCode'] ?? basicsData['OrganizationCode'] ?? orgCode,
|
||||||
|
organizationCode: basicsData['organizationCode'] ?? basicsData['OrganizationCode'] ?? basicsData['code'] ?? basicsData['Code'] ?? orgCode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const localizationData = serverDraft.localization ?? rawRecord['Localization'] ?? rawRecord['localization'] ?? (
|
||||||
|
rawRecord['defaultTimezoneId'] || rawRecord['DefaultTimezoneId'] || rawRecord['defaultCurrencyId'] || rawRecord['DefaultCurrencyId'] || rawRecord['dateFormat'] || rawRecord['DateFormat']
|
||||||
|
? rawRecord
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
|
const planLimitsData = serverDraft.planLimits ?? rawRecord['PlanLimits'] ?? rawRecord['planLimits'] ?? rawRecord['plan'] ?? rawRecord['Plan'] ?? (
|
||||||
|
rawRecord['planId'] || rawRecord['PlanId'] || rawRecord['licenseType'] || rawRecord['LicenseType'] || rawRecord['maxCompanies'] || rawRecord['MaxCompanies']
|
||||||
|
? rawRecord
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
|
const adminData = serverDraft.admin ?? rawRecord['Admin'] ?? rawRecord['admin'] ?? rawRecord['adminContact'] ?? rawRecord['AdminContact'] ?? (
|
||||||
|
rawRecord['adminEmail'] || rawRecord['AdminEmail'] || rawRecord['orgEmail'] || rawRecord['OrgEmail'] || rawRecord['administratorEmail'] || rawRecord['AdministratorEmail']
|
||||||
|
? rawRecord
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
this.onboardingDraftState.set({
|
this.onboardingDraftState.set({
|
||||||
basics: serverDraft.basics ? { ...serverDraft.basics } : null,
|
basics: basicsData ? { ...basicsData } : null,
|
||||||
localization: serverDraft.localization ? { ...serverDraft.localization } : null,
|
localization: localizationData ? { ...localizationData } : null,
|
||||||
planLimits: serverDraft.planLimits ? { ...serverDraft.planLimits } : null,
|
planLimits: planLimitsData ? { ...planLimitsData } : null,
|
||||||
admin: serverDraft.admin ? { ...serverDraft.admin } : null,
|
admin: adminData ? { ...adminData } : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const completedIndexes = serverDraft.completedStepIndexes ?? [];
|
const completedIndexes = isEditMode
|
||||||
|
? [0, 1, 2, 3]
|
||||||
|
: (serverDraft.completedStepIndexes ?? []);
|
||||||
|
|
||||||
this.onboardingDataState.set({
|
this.onboardingDataState.set({
|
||||||
basics: completedIndexes.includes(0) && serverDraft.basics
|
basics: (isEditMode || completedIndexes.includes(0)) && basicsData
|
||||||
? { ...serverDraft.basics } as OrganizationBasicsValue
|
? { ...basicsData } as OrganizationBasicsValue
|
||||||
: null,
|
: null,
|
||||||
localization: completedIndexes.includes(1) && serverDraft.localization
|
localization: (isEditMode || completedIndexes.includes(1)) && localizationData
|
||||||
? { ...serverDraft.localization } as OrganizationLocalizationValue
|
? { ...localizationData } as OrganizationLocalizationValue
|
||||||
: null,
|
: null,
|
||||||
planLimits: completedIndexes.includes(2) && serverDraft.planLimits
|
planLimits: (isEditMode || completedIndexes.includes(2)) && planLimitsData
|
||||||
? { ...serverDraft.planLimits } as OrganizationPlanLimitsValue
|
? { ...planLimitsData } as OrganizationPlanLimitsValue
|
||||||
: null,
|
: null,
|
||||||
admin: completedIndexes.includes(3) && serverDraft.admin
|
admin: (isEditMode || completedIndexes.includes(3)) && adminData
|
||||||
? { ...serverDraft.admin } as OrganizationAdminValue
|
? { ...adminData } as OrganizationAdminValue
|
||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -227,6 +273,7 @@ export class OrganizationOnboardingStateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
|
this.isEditModeState.set(false);
|
||||||
this.organizationIdState.set(null);
|
this.organizationIdState.set(null);
|
||||||
this.onboardingDataState.set(INITIAL_DATA);
|
this.onboardingDataState.set(INITIAL_DATA);
|
||||||
this.onboardingDraftState.set(INITIAL_DRAFT_STATE);
|
this.onboardingDraftState.set(INITIAL_DRAFT_STATE);
|
||||||
|
|||||||
+8
@@ -341,6 +341,9 @@ export function mapPlanStepToApiRequest(
|
|||||||
export interface UpdateOrganizationAdminContactApiRequest {
|
export interface UpdateOrganizationAdminContactApiRequest {
|
||||||
AdminEmail?: string | null;
|
AdminEmail?: string | null;
|
||||||
AdminFullName?: string | null;
|
AdminFullName?: string | null;
|
||||||
|
AdminMobile?: string | null;
|
||||||
|
AdminPhone?: string | null;
|
||||||
|
AdministratorMobile?: string | null;
|
||||||
OrgEmail?: string | null;
|
OrgEmail?: string | null;
|
||||||
OrgPhone?: string | null;
|
OrgPhone?: string | null;
|
||||||
MarkComplete: boolean;
|
MarkComplete: boolean;
|
||||||
@@ -352,9 +355,14 @@ export function mapAdminContactStepToApiRequest(
|
|||||||
admin: Partial<OrganizationAdminValue>,
|
admin: Partial<OrganizationAdminValue>,
|
||||||
markComplete: boolean
|
markComplete: boolean
|
||||||
): UpdateOrganizationAdminContactApiRequest {
|
): UpdateOrganizationAdminContactApiRequest {
|
||||||
|
const mobile = admin.administratorMobile || (admin as any)?.adminMobile || (admin as any)?.AdminMobile || (admin as any)?.adminPhone || (admin as any)?.AdminPhone || null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
AdminEmail: admin.administratorEmail || null,
|
AdminEmail: admin.administratorEmail || null,
|
||||||
AdminFullName: admin.administratorFullName || null,
|
AdminFullName: admin.administratorFullName || null,
|
||||||
|
AdminMobile: mobile,
|
||||||
|
AdminPhone: mobile,
|
||||||
|
AdministratorMobile: mobile,
|
||||||
OrgEmail: admin.organizationEmail || null,
|
OrgEmail: admin.organizationEmail || null,
|
||||||
OrgPhone: admin.organizationPhone || null,
|
OrgPhone: admin.organizationPhone || null,
|
||||||
MarkComplete: markComplete,
|
MarkComplete: markComplete,
|
||||||
|
|||||||
@@ -10,10 +10,12 @@
|
|||||||
[savingDraft]="savingDraft()"
|
[savingDraft]="savingDraft()"
|
||||||
[finishing]="finishing()"
|
[finishing]="finishing()"
|
||||||
[navigationDisabled]="navigationDisabled()"
|
[navigationDisabled]="navigationDisabled()"
|
||||||
|
[isEditMode]="stateService.isEditMode()"
|
||||||
(stepSelected)="onStepSelected($event)"
|
(stepSelected)="onStepSelected($event)"
|
||||||
(backClicked)="onBack()"
|
(backClicked)="onBack()"
|
||||||
(nextClicked)="onNext()"
|
(nextClicked)="onNext()"
|
||||||
(saveDraftClicked)="onSaveDraft()"
|
(saveDraftClicked)="onSaveDraft()"
|
||||||
|
(saveChangesClicked)="onUpdateCurrentStep()"
|
||||||
(finishClicked)="onFinish()"
|
(finishClicked)="onFinish()"
|
||||||
(cancelClicked)="onCancel()"
|
(cancelClicked)="onCancel()"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { catchError, finalize, Observable, of, switchMap } from 'rxjs';
|
import { catchError, finalize, forkJoin, Observable, of, switchMap } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
OnboardingStep,
|
OnboardingStep,
|
||||||
OnboardingStepper
|
OnboardingStepper
|
||||||
@@ -87,18 +87,19 @@ export class OrganizationOnboarding {
|
|||||||
const draftId = this.route.snapshot.queryParamMap.get('id');
|
const draftId = this.route.snapshot.queryParamMap.get('id');
|
||||||
|
|
||||||
if (draftId) {
|
if (draftId) {
|
||||||
|
this.stateService.setIsEditMode(true);
|
||||||
this.onboardingService.getOrganizationById(draftId)
|
this.onboardingService.getOrganizationById(draftId)
|
||||||
.pipe(
|
.pipe(
|
||||||
catchError(err => {
|
catchError(err => {
|
||||||
const errorMsg = err?.error?.detail || err?.error?.message || 'Unable to restore draft from server.';
|
const errorMsg = err?.error?.detail || err?.error?.message || 'Unable to restore organization details from server.';
|
||||||
this.toastr.error(errorMsg, 'Draft Restore Failed');
|
this.toastr.error(errorMsg, 'Organization Restoration Failed');
|
||||||
return of(null);
|
return of(null);
|
||||||
}),
|
}),
|
||||||
takeUntilDestroyed(this.destroyRef)
|
takeUntilDestroyed(this.destroyRef)
|
||||||
)
|
)
|
||||||
.subscribe(draft => {
|
.subscribe(draft => {
|
||||||
if (draft) {
|
if (draft) {
|
||||||
this.stateService.restoreServerDraft(draft);
|
this.stateService.restoreServerDraft(draft, true);
|
||||||
this.savedDraftSnapshot = this.serializeDraftState();
|
this.savedDraftSnapshot = this.serializeDraftState();
|
||||||
this.hydrateActiveStep();
|
this.hydrateActiveStep();
|
||||||
}
|
}
|
||||||
@@ -197,7 +198,12 @@ export class OrganizationOnboarding {
|
|||||||
|
|
||||||
const data = this.stateService.onboardingData();
|
const data = this.stateService.onboardingData();
|
||||||
if (!data.basics || !data.localization || !data.planLimits || !data.admin) {
|
if (!data.basics || !data.localization || !data.planLimits || !data.admin) {
|
||||||
this.toastr.error('Complete and validate every onboarding step before provisioning.');
|
this.toastr.error('Complete and validate every onboarding step before saving.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stateService.isEditMode()) {
|
||||||
|
this.onUpdateCurrentStep();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,6 +222,87 @@ export class OrganizationOnboarding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onUpdateCurrentStep(): void {
|
||||||
|
if (this.finishing() || this.savingDraft()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgId = this.stateService.organizationId();
|
||||||
|
if (!orgId) {
|
||||||
|
this.toastr.error('Organization ID is missing. Unable to update step details.', 'Update Failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentIndex = this.currentStepIndex();
|
||||||
|
const activeStepComponent = this.getStepComponent(currentIndex);
|
||||||
|
|
||||||
|
if (!activeStepComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activeStepComponent.validate()) {
|
||||||
|
const stepLabel = this.steps[currentIndex]?.label ?? `Step ${currentIndex + 1}`;
|
||||||
|
this.toastr.warning(
|
||||||
|
`Please fix the validation errors in ${stepLabel} before saving.`,
|
||||||
|
'Validation Required'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.storeValidatedStep(currentIndex, activeStepComponent);
|
||||||
|
this.finishing.set(true);
|
||||||
|
|
||||||
|
const currentStepLabel = this.steps[currentIndex]?.label ?? `Step ${currentIndex + 1}`;
|
||||||
|
let update$: Observable<unknown>;
|
||||||
|
|
||||||
|
switch (currentIndex) {
|
||||||
|
case 0: {
|
||||||
|
const data = (activeStepComponent as OrganizationBasicsStepComponent).getValue();
|
||||||
|
const payload = mapBasicsStepToApiRequest(data, true);
|
||||||
|
update$ = this.onboardingService.updateBasics(orgId, payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
const data = (activeStepComponent as OrganizationLocalizationStepComponent).getValue();
|
||||||
|
const payload = mapLocalizationStepToApiRequest(data, true);
|
||||||
|
update$ = this.onboardingService.updateLocalization(orgId, payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
const data = (activeStepComponent as OrganizationPlanLimitsStepComponent).getValue();
|
||||||
|
const payload = mapPlanStepToApiRequest(data, true);
|
||||||
|
update$ = this.onboardingService.updatePlan(orgId, payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
const data = (activeStepComponent as OrganizationAdminStepComponent).getValue();
|
||||||
|
const payload = mapAdminContactStepToApiRequest(data, true);
|
||||||
|
update$ = this.onboardingService.updateAdminContact(orgId, payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
update$ = of(null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
update$
|
||||||
|
.pipe(
|
||||||
|
finalize(() => this.finishing.set(false)),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.stateService.markStepCompleted(currentIndex);
|
||||||
|
this.savedDraftSnapshot = this.serializeDraftState();
|
||||||
|
this.toastr.success(`${currentStepLabel} details updated successfully.`, 'Changes Saved');
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
const errorMsg = err?.error?.detail || err?.error?.message || `Failed to update ${currentStepLabel.toLowerCase()} details.`;
|
||||||
|
this.toastr.error(errorMsg, 'Update Failed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onCancel(): void {
|
onCancel(): void {
|
||||||
this.captureActiveStepDraft();
|
this.captureActiveStepDraft();
|
||||||
if (this.serializeDraftState() !== this.savedDraftSnapshot) {
|
if (this.serializeDraftState() !== this.savedDraftSnapshot) {
|
||||||
@@ -399,7 +486,6 @@ export class OrganizationOnboarding {
|
|||||||
|
|
||||||
private captureActiveStepDraft(): void {
|
private captureActiveStepDraft(): void {
|
||||||
const currentIndex = this.currentStepIndex();
|
const currentIndex = this.currentStepIndex();
|
||||||
const validatedValue = this.getValidatedStepValue(currentIndex);
|
|
||||||
|
|
||||||
switch (currentIndex) {
|
switch (currentIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -415,14 +501,6 @@ export class OrganizationOnboarding {
|
|||||||
this.stateService.updateAdminDraft(this.adminStep()?.getDraftValue() ?? null);
|
this.stateService.updateAdminDraft(this.adminStep()?.getDraftValue() ?? null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const draftValue = this.getDraftStepValue(currentIndex);
|
|
||||||
if (
|
|
||||||
this.stateService.isStepCompleted(currentIndex) &&
|
|
||||||
JSON.stringify(draftValue) !== JSON.stringify(validatedValue)
|
|
||||||
) {
|
|
||||||
this.stateService.invalidateStep(currentIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getValidatedStepValue(index: number): unknown {
|
private getValidatedStepValue(index: number): unknown {
|
||||||
|
|||||||
+6
-16
@@ -109,47 +109,37 @@
|
|||||||
<div>
|
<div>
|
||||||
<dt class="text-xs text-textmuted">Organization</dt>
|
<dt class="text-xs text-textmuted">Organization</dt>
|
||||||
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
||||||
{{ reviewData().basics?.organizationName || 'Not completed' }}
|
{{ organizationNameSummary() }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-xs text-textmuted">Country</dt>
|
<dt class="text-xs text-textmuted">Country</dt>
|
||||||
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
||||||
{{ reviewData().basics?.registrationCountry?.label || 'Not completed' }}
|
{{ countrySummary() }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-xs text-textmuted">Localization</dt>
|
<dt class="text-xs text-textmuted">Localization</dt>
|
||||||
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
||||||
{{ reviewData().localization?.timeZone?.label || 'Not completed' }} ·
|
{{ localizationSummary() }}
|
||||||
{{ reviewData().localization?.currency?.secondaryLabel || reviewData().localization?.currency?.label || '—' }}
|
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-xs text-textmuted">Plan and limits</dt>
|
<dt class="text-xs text-textmuted">Plan and limits</dt>
|
||||||
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
||||||
{{ reviewData().planLimits?.subscriptionPlan?.label || 'Not completed' }}
|
{{ planSummary() }}
|
||||||
@if (reviewData().planLimits) {
|
|
||||||
· {{ reviewData().planLimits?.maximumCompanies }} companies ·
|
|
||||||
{{ reviewData().planLimits?.maximumUsers }} users ·
|
|
||||||
{{ reviewData().planLimits?.maximumStorageGb }} GB
|
|
||||||
}
|
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-xs text-textmuted">Access dates</dt>
|
<dt class="text-xs text-textmuted">Access dates</dt>
|
||||||
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
||||||
{{ reviewData().planLimits?.systemAccessStartDate || 'Not completed' }} –
|
{{ accessDatesSummary() }}
|
||||||
{{ reviewData().planLimits?.systemAccessEndDate || 'No end date' }}
|
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-xs text-textmuted">Administrator</dt>
|
<dt class="text-xs text-textmuted">Administrator</dt>
|
||||||
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
<dd class="font-medium text-defaulttextcolor dark:text-white">
|
||||||
{{ form.controls.administratorFullName.value || 'Not entered' }}
|
{{ adminSummary() }}
|
||||||
@if (form.controls.administratorEmail.value) {
|
|
||||||
· {{ form.controls.administratorEmail.value }}
|
|
||||||
}
|
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
|
|||||||
+90
-5
@@ -2,10 +2,12 @@ import {
|
|||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
|
computed,
|
||||||
inject,
|
inject,
|
||||||
input,
|
input,
|
||||||
signal,
|
signal,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
|
||||||
import { FormInput } from '../../../../../shared/components/form/form-input/form-input';
|
import { FormInput } from '../../../../../shared/components/form/form-input/form-input';
|
||||||
@@ -45,6 +47,73 @@ export class OrganizationAdminStepComponent implements OnboardingStepForm<Organi
|
|||||||
administratorMobile: ['', [Validators.required]],
|
administratorMobile: ['', [Validators.required]],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private readonly formValues = toSignal(this.form.valueChanges, {
|
||||||
|
initialValue: this.form.getRawValue(),
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly organizationNameSummary = computed(() => {
|
||||||
|
const basics = this.reviewData()?.basics as Record<string, any> | null;
|
||||||
|
return (
|
||||||
|
basics?.['organizationName'] ||
|
||||||
|
basics?.['name'] ||
|
||||||
|
basics?.['OrganizationName'] ||
|
||||||
|
basics?.['Name'] ||
|
||||||
|
'Not completed'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly countrySummary = computed(() => {
|
||||||
|
const basics = this.reviewData()?.basics as Record<string, any> | null;
|
||||||
|
const countryObj = basics?.['registrationCountry'];
|
||||||
|
const countryLabel = countryObj?.['label'] || countryObj?.['name'];
|
||||||
|
return (
|
||||||
|
countryLabel ||
|
||||||
|
basics?.['countryName'] ||
|
||||||
|
basics?.['CountryName'] ||
|
||||||
|
basics?.['country'] ||
|
||||||
|
basics?.['Country'] ||
|
||||||
|
'Not completed'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly localizationSummary = computed(() => {
|
||||||
|
const loc = this.reviewData()?.localization as Record<string, any> | null;
|
||||||
|
if (!loc) return 'Not completed';
|
||||||
|
const tz = loc['timeZone']?.label || loc['defaultTimezoneName'] || loc['DefaultTimezoneName'] || 'Not completed';
|
||||||
|
const currency = loc['currency']?.secondaryLabel || loc['currency']?.label || loc['defaultCurrencyName'] || loc['DefaultCurrencyName'] || '—';
|
||||||
|
return `${tz} · ${currency}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly planSummary = computed(() => {
|
||||||
|
const plan = this.reviewData()?.planLimits as Record<string, any> | null;
|
||||||
|
if (!plan) return 'Not completed';
|
||||||
|
const name = plan['subscriptionPlan']?.label || plan['planName'] || plan['PlanName'] || plan['plan'] || 'Not completed';
|
||||||
|
const maxComp = plan['maximumCompanies'] ?? plan['maxCompanies'] ?? plan['MaxCompanies'] ?? 0;
|
||||||
|
const maxUsers = plan['maximumUsers'] ?? plan['maxUsers'] ?? plan['MaxUsers'] ?? 0;
|
||||||
|
const maxStorage = plan['maximumStorageGb'] ?? plan['maxStorageGb'] ?? plan['MaxStorageGb'] ?? 0;
|
||||||
|
return `${name} · ${maxComp} companies · ${maxUsers} users · ${maxStorage} GB`;
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly accessDatesSummary = computed(() => {
|
||||||
|
const plan = this.reviewData()?.planLimits as Record<string, any> | null;
|
||||||
|
if (!plan) return 'Not completed';
|
||||||
|
const start = plan['systemAccessStartDate'] || plan['systemAccessFrom'] || plan['SystemAccessFrom'] || plan['goLiveDate'] || 'Not completed';
|
||||||
|
const end = plan['systemAccessEndDate'] || plan['systemAccessTo'] || plan['SystemAccessTo'] || 'No end date';
|
||||||
|
return `${start} – ${end}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly adminSummary = computed(() => {
|
||||||
|
const formVal = this.formValues();
|
||||||
|
const adminData = this.reviewData()?.admin as Record<string, any> | null;
|
||||||
|
|
||||||
|
const fullName = formVal.administratorFullName || adminData?.['administratorFullName'] || adminData?.['adminFullName'] || adminData?.['AdminFullName'] || '';
|
||||||
|
const email = formVal.administratorEmail || adminData?.['administratorEmail'] || adminData?.['adminEmail'] || adminData?.['AdminEmail'] || '';
|
||||||
|
const mobile = formVal.administratorMobile || adminData?.['administratorMobile'] || adminData?.['adminMobile'] || adminData?.['AdminMobile'] || adminData?.['adminPhone'] || adminData?.['AdminPhone'] || '';
|
||||||
|
|
||||||
|
const parts = [fullName, email, mobile].filter(Boolean);
|
||||||
|
return parts.length > 0 ? parts.join(' · ') : 'Not entered';
|
||||||
|
});
|
||||||
|
|
||||||
validate(): boolean {
|
validate(): boolean {
|
||||||
this.submitAttempted.set(true);
|
this.submitAttempted.set(true);
|
||||||
if (this.form.valid) {
|
if (this.form.valid) {
|
||||||
@@ -71,12 +140,28 @@ export class OrganizationAdminStepComponent implements OnboardingStepForm<Organi
|
|||||||
}
|
}
|
||||||
|
|
||||||
patchValue(value: Partial<OrganizationAdminValue> & Record<string, any>): void {
|
patchValue(value: Partial<OrganizationAdminValue> & Record<string, any>): void {
|
||||||
|
if (!value) return;
|
||||||
|
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
organizationEmail: value.organizationEmail ?? value['OrgEmail'] ?? value['orgEmail'] ?? '',
|
organizationEmail: value.organizationEmail ?? value['OrgEmail'] ?? value['orgEmail'] ?? value['OrganizationEmail'] ?? '',
|
||||||
organizationPhone: value.organizationPhone ?? value['OrgPhone'] ?? value['orgPhone'] ?? '',
|
organizationPhone: value.organizationPhone ?? value['OrgPhone'] ?? value['orgPhone'] ?? value['OrganizationPhone'] ?? '',
|
||||||
administratorFullName: value.administratorFullName ?? value['AdminFullName'] ?? value['adminFullName'] ?? '',
|
administratorFullName: value.administratorFullName ?? value['AdminFullName'] ?? value['adminFullName'] ?? value['AdministratorFullName'] ?? '',
|
||||||
administratorEmail: value.administratorEmail ?? value['AdminEmail'] ?? value['adminEmail'] ?? '',
|
administratorEmail: value.administratorEmail ?? value['AdminEmail'] ?? value['adminEmail'] ?? value['AdministratorEmail'] ?? '',
|
||||||
administratorMobile: value.administratorMobile ?? value['AdminMobile'] ?? value['adminMobile'] ?? '',
|
administratorMobile:
|
||||||
|
value.administratorMobile ??
|
||||||
|
value['AdminMobile'] ??
|
||||||
|
value['adminMobile'] ??
|
||||||
|
value['AdministratorMobile'] ??
|
||||||
|
value['administratorMobile'] ??
|
||||||
|
value['AdminPhone'] ??
|
||||||
|
value['adminPhone'] ??
|
||||||
|
value['AdministratorPhone'] ??
|
||||||
|
value['administratorPhone'] ??
|
||||||
|
value['mobile'] ??
|
||||||
|
value['Mobile'] ??
|
||||||
|
value['phone'] ??
|
||||||
|
value['Phone'] ??
|
||||||
|
'',
|
||||||
}, { emitEvent: false });
|
}, { emitEvent: false });
|
||||||
this.submitAttempted.set(false);
|
this.submitAttempted.set(false);
|
||||||
}
|
}
|
||||||
|
|||||||
+116
-26
@@ -1,4 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, Component, ElementRef, inject, signal } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, inject, signal } from '@angular/core';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { catchError, map, of } from 'rxjs';
|
import { catchError, map, of } from 'rxjs';
|
||||||
@@ -24,8 +25,10 @@ import {
|
|||||||
OrganizationBasicsValue,
|
OrganizationBasicsValue,
|
||||||
} from '../../models/organization-onboarding.model';
|
} from '../../models/organization-onboarding.model';
|
||||||
import { OrganizationOnboardingService } from '../../data-access/services/organization-onboarding.service';
|
import { OrganizationOnboardingService } from '../../data-access/services/organization-onboarding.service';
|
||||||
|
import { OrganizationOnboardingStateService } from '../../data-access/services/organization-onboarding-state.service';
|
||||||
|
|
||||||
interface OrganizationBasicsFormModel {
|
interface OrganizationBasicsFormModel {
|
||||||
|
readonly code: string | null;
|
||||||
readonly organizationName: string;
|
readonly organizationName: string;
|
||||||
readonly shortName: string;
|
readonly shortName: string;
|
||||||
readonly localLanguageName: string;
|
readonly localLanguageName: string;
|
||||||
@@ -42,6 +45,7 @@ interface OrganizationBasicsFormModel {
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class OrganizationBasicsStepComponent implements OnboardingStepForm<OrganizationBasicsValue> {
|
export class OrganizationBasicsStepComponent implements OnboardingStepForm<OrganizationBasicsValue> {
|
||||||
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
private readonly formBuilder = inject(FormBuilder);
|
private readonly formBuilder = inject(FormBuilder);
|
||||||
private readonly onboardingService = inject(OrganizationOnboardingService);
|
private readonly onboardingService = inject(OrganizationOnboardingService);
|
||||||
private readonly countryService = inject(CountryService);
|
private readonly countryService = inject(CountryService);
|
||||||
@@ -49,6 +53,8 @@ export class OrganizationBasicsStepComponent implements OnboardingStepForm<Organ
|
|||||||
private readonly toastr = inject(ToastrService);
|
private readonly toastr = inject(ToastrService);
|
||||||
private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
|
private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
|
||||||
|
|
||||||
|
private readonly stateService = inject(OrganizationOnboardingStateService, { optional: true });
|
||||||
|
|
||||||
readonly submitAttempted = signal(false);
|
readonly submitAttempted = signal(false);
|
||||||
readonly organizationCodeControl = this.formBuilder.nonNullable.control('Pending generation');
|
readonly organizationCodeControl = this.formBuilder.nonNullable.control('Pending generation');
|
||||||
readonly organizationTypeSelection = signal<OnboardingLookupValue | null>(null);
|
readonly organizationTypeSelection = signal<OnboardingLookupValue | null>(null);
|
||||||
@@ -152,8 +158,11 @@ export class OrganizationBasicsStepComponent implements OnboardingStepForm<Organ
|
|||||||
throw new Error('Organization basics selections are incomplete.');
|
throw new Error('Organization basics selections are incomplete.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const codeControlVal = this.organizationCodeControl.value;
|
||||||
|
const codeVal = codeControlVal && codeControlVal !== 'Pending generation' ? codeControlVal : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
organizationCode: null,
|
organizationCode: codeVal,
|
||||||
organizationName: value.organizationName.trim(),
|
organizationName: value.organizationName.trim(),
|
||||||
shortName: value.shortName.trim(),
|
shortName: value.shortName.trim(),
|
||||||
localLanguageName: value.localLanguageName.trim() || null,
|
localLanguageName: value.localLanguageName.trim() || null,
|
||||||
@@ -169,45 +178,126 @@ export class OrganizationBasicsStepComponent implements OnboardingStepForm<Organ
|
|||||||
|
|
||||||
getDraftValue(): Partial<OrganizationBasicsValue> {
|
getDraftValue(): Partial<OrganizationBasicsValue> {
|
||||||
const value = this.form.getRawValue() as OrganizationBasicsFormModel;
|
const value = this.form.getRawValue() as OrganizationBasicsFormModel;
|
||||||
|
const country = this.registrationCountrySelection();
|
||||||
|
const codeControlVal = this.organizationCodeControl.value;
|
||||||
|
const codeVal = codeControlVal && codeControlVal !== 'Pending generation' ? codeControlVal : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
organizationCode: null,
|
organizationCode: codeVal,
|
||||||
organizationName: value.organizationName,
|
organizationName: value.organizationName,
|
||||||
shortName: value.shortName,
|
shortName: value.shortName,
|
||||||
localLanguageName: value.localLanguageName || null,
|
localLanguageName: value.localLanguageName || null,
|
||||||
organizationType: this.organizationTypeSelection() ?? undefined,
|
organizationType: this.organizationTypeSelection() ?? undefined,
|
||||||
industry: this.industrySelection() ?? undefined,
|
industry: this.industrySelection() ?? undefined,
|
||||||
registrationCountry: this.registrationCountrySelection()
|
registrationCountry: country
|
||||||
? {
|
? {
|
||||||
id: this.registrationCountrySelection()!.id,
|
id: country.id,
|
||||||
label: this.registrationCountrySelection()!.name,
|
label: country.name,
|
||||||
iso2: this.registrationCountrySelection()!.iso2,
|
name: country.name,
|
||||||
}
|
iso2: country.iso2,
|
||||||
|
} as any
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
patchValue(value: Partial<OrganizationBasicsValue>): void {
|
patchValue(value: Partial<OrganizationBasicsValue> & Record<string, any>): void {
|
||||||
|
if (!value) return;
|
||||||
|
|
||||||
|
const draftBasics = this.stateService?.onboardingDraft()?.basics as Record<string, any> | null;
|
||||||
|
const orgCode =
|
||||||
|
value.organizationCode ??
|
||||||
|
value['organizationCode'] ??
|
||||||
|
value['OrganizationCode'] ??
|
||||||
|
value['code'] ??
|
||||||
|
value['Code'] ??
|
||||||
|
draftBasics?.['organizationCode'] ??
|
||||||
|
draftBasics?.['code'] ??
|
||||||
|
draftBasics?.['Code'] ??
|
||||||
|
null;
|
||||||
|
|
||||||
|
if (orgCode) {
|
||||||
|
this.organizationCodeControl.setValue(String(orgCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgName = value.organizationName ?? value['OrganizationName'] ?? value['name'] ?? value['Name'] ?? '';
|
||||||
|
const shortName = value.shortName ?? value['ShortName'] ?? '';
|
||||||
|
const localLanguageName = value.localLanguageName ?? value['LocalLanguageName'] ?? '';
|
||||||
|
|
||||||
|
// 1. Organization Type
|
||||||
|
const rawOrgTypeId = value.organizationType?.id ?? value['organizationTypeId'] ?? value['OrganizationTypeId'] ?? value['orgType'] ?? value['OrgType'] ?? null;
|
||||||
|
const orgTypeParsedId = rawOrgTypeId != null ? String(rawOrgTypeId) : null;
|
||||||
|
const orgTypeLabel = value.organizationType?.label ?? value['organizationTypeName'] ?? value['OrganizationTypeName'] ?? '';
|
||||||
|
|
||||||
|
// 2. Industry
|
||||||
|
const rawIndustryId = value.industry?.id ?? value['industryId'] ?? value['IndustryId'] ?? null;
|
||||||
|
const industryId = rawIndustryId ? String(rawIndustryId) : null;
|
||||||
|
const industryLabel = value.industry?.label ?? value['industryName'] ?? value['IndustryName'] ?? '';
|
||||||
|
|
||||||
|
// 3. Country
|
||||||
|
const rawCountryId = value.registrationCountry?.id ?? (value.registrationCountry as any)?.id ?? value['registrationCountryId'] ?? value['RegistrationCountryId'] ?? value['countryId'] ?? value['CountryId'] ?? null;
|
||||||
|
const countryId = rawCountryId ? String(rawCountryId) : null;
|
||||||
|
const countryName = value.registrationCountry?.label ?? (value.registrationCountry as any)?.name ?? value['registrationCountryName'] ?? value['RegistrationCountryName'] ?? value['countryName'] ?? value['CountryName'] ?? '';
|
||||||
|
const countryIso2 = value.registrationCountry?.iso2 ?? value['registrationCountryIso2'] ?? value['RegistrationCountryIso2'] ?? value['countryIso2'] ?? value['CountryIso2'] ?? '';
|
||||||
|
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
organizationName: value.organizationName ?? '',
|
organizationName: orgName,
|
||||||
shortName: value.shortName ?? '',
|
shortName: shortName,
|
||||||
localLanguageName: value.localLanguageName ?? '',
|
localLanguageName: localLanguageName,
|
||||||
organizationTypeId: value.organizationType?.id ?? null,
|
organizationTypeId: orgTypeParsedId,
|
||||||
industryId: value.industry?.id ?? null,
|
industryId: industryId,
|
||||||
registrationCountryId: value.registrationCountry?.id ?? null,
|
registrationCountryId: countryId,
|
||||||
}, { emitEvent: false });
|
}, { emitEvent: false });
|
||||||
|
|
||||||
this.organizationTypeSelection.set(value.organizationType ?? null);
|
// Handle Organization Type Selection
|
||||||
this.industrySelection.set(value.industry ?? null);
|
if (orgTypeParsedId != null) {
|
||||||
this.registrationCountrySelection.set(
|
if (orgTypeLabel) {
|
||||||
value.registrationCountry
|
this.organizationTypeSelection.set({ id: orgTypeParsedId, label: orgTypeLabel });
|
||||||
? {
|
} else {
|
||||||
id: value.registrationCountry.id,
|
this.onboardingService.resolveOrganizationType(orgTypeParsedId).pipe(
|
||||||
iso2: value.registrationCountry.iso2 ?? '',
|
catchError(() => of(null)),
|
||||||
name: value.registrationCountry.label,
|
takeUntilDestroyed(this.destroyRef)
|
||||||
}
|
).subscribe(resolved => {
|
||||||
: null
|
if (resolved) this.organizationTypeSelection.set(resolved);
|
||||||
);
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.organizationTypeSelection.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Industry Selection
|
||||||
|
if (industryId) {
|
||||||
|
if (industryLabel) {
|
||||||
|
this.industrySelection.set({ id: industryId, label: industryLabel, secondaryLabel: value.industry?.secondaryLabel });
|
||||||
|
} else {
|
||||||
|
this.industryApiService.getIndustryById(industryId).pipe(
|
||||||
|
map(item => item ? ({ id: item.id, label: item.industryName, secondaryLabel: item.industryCode }) : null),
|
||||||
|
catchError(() => of(null)),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
).subscribe(resolved => {
|
||||||
|
if (resolved) this.industrySelection.set(resolved);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.industrySelection.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Country Selection
|
||||||
|
if (countryId) {
|
||||||
|
if (countryName) {
|
||||||
|
this.registrationCountrySelection.set({ id: countryId, iso2: countryIso2, name: countryName });
|
||||||
|
} else {
|
||||||
|
this.countryService.getCountryById(countryId).pipe(
|
||||||
|
map(country => ({ id: country.id, iso2: country.iso2, name: country.name })),
|
||||||
|
catchError(() => of(null)),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
).subscribe(resolved => {
|
||||||
|
if (resolved) this.registrationCountrySelection.set(resolved);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.registrationCountrySelection.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
this.submitAttempted.set(false);
|
this.submitAttempted.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+87
-37
@@ -289,12 +289,20 @@ export class OrganizationLocalizationStepComponent implements OnboardingStepForm
|
|||||||
}
|
}
|
||||||
|
|
||||||
patchValue(value: Partial<OrganizationLocalizationValue> & Record<string, any>): void {
|
patchValue(value: Partial<OrganizationLocalizationValue> & Record<string, any>): void {
|
||||||
|
if (!value) return;
|
||||||
|
|
||||||
|
const timeZoneId = value.timeZone?.id ?? value['DefaultTimezoneId'] ?? value['defaultTimezoneId'] ?? value['timeZoneId'] ?? null;
|
||||||
|
const currencyId = value.currency?.id ?? value['DefaultCurrencyId'] ?? value['defaultCurrencyId'] ?? value['currencyId'] ?? null;
|
||||||
|
const defaultLanguageId = value.defaultLanguage?.id ?? value['DefaultLanguageId'] ?? value['defaultLanguageId'] ?? value['languageId'] ?? null;
|
||||||
|
const additionalLanguageIds = value.additionalLanguageIds ?? value['AdditionalLanguageIds'] ?? value['additionalLanguageIds'] ?? [];
|
||||||
|
const dateFormat = value.dateFormat ?? value['DateFormat'] ?? value['dateFormat'] ?? null;
|
||||||
|
|
||||||
const rawTf = value.timeFormat !== undefined && value.timeFormat !== null ? value.timeFormat : value['TimeFormat'] ?? value['timeFormat'];
|
const rawTf = value.timeFormat !== undefined && value.timeFormat !== null ? value.timeFormat : value['TimeFormat'] ?? value['timeFormat'];
|
||||||
const timeFormatNormalized: TimeFormatValue | null =
|
const timeFormatNormalized: TimeFormatValue | null =
|
||||||
rawTf === 1 || rawTf === '1' || rawTf === 'TwentyFourHour'
|
rawTf === 1 || rawTf === '1' || rawTf === 'TwentyFourHour'
|
||||||
? 1
|
? 'TwentyFourHour'
|
||||||
: rawTf === 0 || rawTf === '0' || rawTf === 'TwelveHour'
|
: rawTf === 0 || rawTf === '0' || rawTf === 'TwelveHour'
|
||||||
? 0
|
? 'TwelveHour'
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const rawFiscalYear = value.fiscalYearConvention ?? value['FiscalYearStart'] ?? value['fiscalYearStart'] ?? null;
|
const rawFiscalYear = value.fiscalYearConvention ?? value['FiscalYearStart'] ?? value['fiscalYearStart'] ?? null;
|
||||||
@@ -304,46 +312,88 @@ export class OrganizationLocalizationStepComponent implements OnboardingStepForm
|
|||||||
const numberFormatNormalized = mapApiToNumberFormat(rawNumberFormat);
|
const numberFormatNormalized = mapApiToNumberFormat(rawNumberFormat);
|
||||||
|
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
timeZoneId: value.timeZone?.id ?? value['DefaultTimezoneId'] ?? value['defaultTimezoneId'] ?? value['timeZoneId'] ?? null,
|
timeZoneId,
|
||||||
currencyId: value.currency?.id ?? value['DefaultCurrencyId'] ?? value['defaultCurrencyId'] ?? value['currencyId'] ?? null,
|
currencyId,
|
||||||
defaultLanguageId: value.defaultLanguage?.id ?? value['DefaultLanguageId'] ?? value['defaultLanguageId'] ?? value['defaultLanguageId'] ?? null,
|
defaultLanguageId,
|
||||||
additionalLanguageIds: value.additionalLanguageIds ?? value['AdditionalLanguageIds'] ?? value['additionalLanguageIds'] ?? [],
|
additionalLanguageIds,
|
||||||
dateFormat: value.dateFormat ?? value['DateFormat'] ?? value['dateFormat'] ?? null,
|
dateFormat,
|
||||||
timeFormat: timeFormatNormalized,
|
timeFormat: timeFormatNormalized,
|
||||||
numberFormat: numberFormatNormalized,
|
numberFormat: numberFormatNormalized,
|
||||||
fiscalYearConvention: fiscalYearNormalized,
|
fiscalYearConvention: fiscalYearNormalized,
|
||||||
}, { emitEvent: false });
|
}, { emitEvent: false });
|
||||||
|
|
||||||
this.timeZoneSelection.set(
|
// Timezone Selection
|
||||||
value.timeZone
|
if (value.timeZone) {
|
||||||
? {
|
this.timeZoneSelection.set({
|
||||||
id: value.timeZone.id,
|
id: value.timeZone.id,
|
||||||
ianaId: value.timeZone.secondaryLabel ?? value.timeZone.label,
|
ianaId: value.timeZone.secondaryLabel ?? value.timeZone.label,
|
||||||
displayName: value.timeZone.label,
|
displayName: value.timeZone.label,
|
||||||
}
|
});
|
||||||
: null
|
} else if (timeZoneId) {
|
||||||
);
|
const tzName = value['DefaultTimezoneName'] ?? value['defaultTimezoneName'] ?? value['timeZoneName'] ?? '';
|
||||||
this.currencySelection.set(
|
if (tzName) {
|
||||||
value.currency
|
this.timeZoneSelection.set({ id: timeZoneId, ianaId: tzName, displayName: tzName });
|
||||||
? {
|
} else {
|
||||||
id: value.currency.id,
|
this.timezoneService.getById(timeZoneId).pipe(
|
||||||
code: value.currency.secondaryLabel ?? value.currency.label,
|
catchError(() => of(null)),
|
||||||
name: value.currency.label,
|
takeUntilDestroyed(this.destroyRef)
|
||||||
symbol: '',
|
).subscribe(tz => {
|
||||||
}
|
if (tz) this.timeZoneSelection.set({ id: tz.id, ianaId: tz.ianaId, displayName: tz.displayName });
|
||||||
: null
|
});
|
||||||
);
|
}
|
||||||
this.defaultLanguageSelection.set(
|
} else {
|
||||||
value.defaultLanguage
|
this.timeZoneSelection.set(null);
|
||||||
? {
|
}
|
||||||
id: value.defaultLanguage.id,
|
|
||||||
code: value.defaultLanguage.secondaryLabel ?? value.defaultLanguage.label,
|
// Currency Selection
|
||||||
name: value.defaultLanguage.label,
|
if (value.currency) {
|
||||||
nativeName: value.defaultLanguage.label,
|
this.currencySelection.set({
|
||||||
isRightToLeft: false,
|
id: value.currency.id,
|
||||||
}
|
code: value.currency.secondaryLabel ?? value.currency.label,
|
||||||
: null
|
name: value.currency.label,
|
||||||
);
|
symbol: '',
|
||||||
|
});
|
||||||
|
} else if (currencyId) {
|
||||||
|
const currName = value['DefaultCurrencyName'] ?? value['defaultCurrencyName'] ?? value['currencyName'] ?? '';
|
||||||
|
if (currName) {
|
||||||
|
this.currencySelection.set({ id: currencyId, code: currName, name: currName, symbol: '' });
|
||||||
|
} else {
|
||||||
|
this.currencyService.getCurrencyById(currencyId).pipe(
|
||||||
|
catchError(() => of(null)),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
).subscribe(c => {
|
||||||
|
if (c) this.currencySelection.set({ id: c.id, code: c.code, name: c.name, symbol: c.symbol });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.currencySelection.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default Language Selection
|
||||||
|
if (value.defaultLanguage) {
|
||||||
|
this.defaultLanguageSelection.set({
|
||||||
|
id: value.defaultLanguage.id,
|
||||||
|
code: value.defaultLanguage.secondaryLabel ?? value.defaultLanguage.label,
|
||||||
|
name: value.defaultLanguage.label,
|
||||||
|
nativeName: value.defaultLanguage.label,
|
||||||
|
isRightToLeft: false,
|
||||||
|
});
|
||||||
|
} else if (defaultLanguageId) {
|
||||||
|
const langName = value['DefaultLanguageName'] ?? value['defaultLanguageName'] ?? value['languageName'] ?? '';
|
||||||
|
if (langName) {
|
||||||
|
this.defaultLanguageSelection.set({ id: defaultLanguageId, code: langName, name: langName, nativeName: langName, isRightToLeft: false });
|
||||||
|
} else {
|
||||||
|
this.languageService.getById(defaultLanguageId).pipe(
|
||||||
|
catchError(() => of(null)),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
).subscribe(l => {
|
||||||
|
if (l) this.defaultLanguageSelection.set({ id: l.id, code: l.code, name: l.name, nativeName: l.nativeName, isRightToLeft: l.isRightToLeft });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.defaultLanguageSelection.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
this.additionalLanguageOptions.update(current => this.mergeSelectionOptions(current, value.additionalLanguageSelections ?? []));
|
this.additionalLanguageOptions.update(current => this.mergeSelectionOptions(current, value.additionalLanguageSelections ?? []));
|
||||||
this.submitAttempted.set(false);
|
this.submitAttempted.set(false);
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-14
@@ -224,32 +224,47 @@ export class OrganizationPlanLimitsStepComponent implements OnboardingStepForm<O
|
|||||||
}
|
}
|
||||||
|
|
||||||
patchValue(value: Partial<OrganizationPlanLimitsValue> & Record<string, any>): void {
|
patchValue(value: Partial<OrganizationPlanLimitsValue> & Record<string, any>): void {
|
||||||
const rawLicenseType = value.licenseType ?? value['LicenseType'];
|
if (!value) return;
|
||||||
|
|
||||||
|
const rawLicenseType = value.licenseType ?? value['LicenseType'] ?? value['licenseType'];
|
||||||
const normalizedLicenseType = mapApiToLicenseType(rawLicenseType);
|
const normalizedLicenseType = mapApiToLicenseType(rawLicenseType);
|
||||||
|
|
||||||
const planId = value.subscriptionPlan?.id ?? value['PlanId'] ?? value['subscriptionPlanId'] ?? null;
|
const planId = value.subscriptionPlan?.id ?? value['PlanId'] ?? value['subscriptionPlanId'] ?? value['planId'] ?? null;
|
||||||
|
|
||||||
const subPlan = value.subscriptionPlan ?? (value['SubscriptionPlan'] ? {
|
const subPlan = value.subscriptionPlan ?? (value['SubscriptionPlan'] || value['plan'] || value['Plan'] ? {
|
||||||
id: value['SubscriptionPlan']?.id ?? planId,
|
id: value['SubscriptionPlan']?.id ?? value['plan']?.id ?? value['Plan']?.id ?? planId,
|
||||||
label: value['SubscriptionPlan']?.name ?? value['SubscriptionPlan']?.label ?? '',
|
label: value['SubscriptionPlan']?.name ?? value['SubscriptionPlan']?.label ?? value['planName'] ?? value['PlanName'] ?? value['plan']?.name ?? '',
|
||||||
code: value['SubscriptionPlan']?.code ?? '',
|
code: value['SubscriptionPlan']?.code ?? value['plan']?.code ?? value['Plan']?.code ?? '',
|
||||||
} : planId ? { id: planId, label: '', code: '' } : null);
|
} : planId ? { id: planId, label: value['PlanName'] ?? value['planName'] ?? '', code: '' } : null);
|
||||||
|
|
||||||
const from = value.systemAccessStartDate ?? value['SystemAccessFrom'] ?? null;
|
const from = value.systemAccessStartDate ?? value['SystemAccessFrom'] ?? value['systemAccessFrom'] ?? value['systemAccessStartDate'] ?? null;
|
||||||
const to = value.systemAccessEndDate ?? value['SystemAccessTo'] ?? null;
|
const to = value.systemAccessEndDate ?? value['SystemAccessTo'] ?? value['systemAccessTo'] ?? value['systemAccessEndDate'] ?? null;
|
||||||
const systemAccessPeriod: DateRangeValue | null = (from || to) ? { from, to } : null;
|
const systemAccessPeriod: DateRangeValue | null = (from || to) ? { from, to } : null;
|
||||||
|
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
subscriptionPlanId: planId,
|
subscriptionPlanId: planId,
|
||||||
licenseType: normalizedLicenseType,
|
licenseType: normalizedLicenseType,
|
||||||
maximumCompanies: value.maximumCompanies ?? value['MaxCompanies'] ?? null,
|
maximumCompanies: value.maximumCompanies ?? value['MaxCompanies'] ?? value['maxCompanies'] ?? null,
|
||||||
maximumUsers: value.maximumUsers ?? value['MaxUsers'] ?? null,
|
maximumUsers: value.maximumUsers ?? value['MaxUsers'] ?? value['maxUsers'] ?? null,
|
||||||
maximumStorageGb: value.maximumStorageGb ?? value['MaxStorageGb'] ?? null,
|
maximumStorageGb: value.maximumStorageGb ?? value['MaxStorageGb'] ?? value['maxStorageGb'] ?? null,
|
||||||
goLiveDate: value.goLiveDate ?? value['GoLiveDate'] ?? null,
|
goLiveDate: value.goLiveDate ?? value['GoLiveDate'] ?? value['goLiveDate'] ?? null,
|
||||||
systemAccessPeriod,
|
systemAccessPeriod,
|
||||||
}, { emitEvent: false });
|
}, { emitEvent: false });
|
||||||
|
|
||||||
this.planSelection.set(subPlan ?? value.subscriptionPlan ?? null);
|
if (subPlan && subPlan.id) {
|
||||||
|
this.planSelection.set(subPlan);
|
||||||
|
if (!subPlan.label && planId) {
|
||||||
|
this.onboardingService.resolveSubscriptionPlan(planId).pipe(
|
||||||
|
catchError(() => of(null)),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
).subscribe(plan => {
|
||||||
|
if (plan) this.planSelection.set(plan);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.planSelection.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
this.limitsEditable.set(value.limitsEditable ?? true);
|
this.limitsEditable.set(value.limitsEditable ?? true);
|
||||||
this.applyLimitsEditableState();
|
this.applyLimitsEditableState();
|
||||||
this.submitAttempted.set(false);
|
this.submitAttempted.set(false);
|
||||||
|
|||||||
@@ -265,10 +265,6 @@ export class TenantCurrencies {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.draw !== query.draw) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows: TenantCurrencyTableRow[] =
|
const rows: TenantCurrencyTableRow[] =
|
||||||
response.rows.map(
|
response.rows.map(
|
||||||
(tenantCurrency, index) => ({
|
(tenantCurrency, index) => ({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
[columns]="columns()"
|
[columns]="columns()"
|
||||||
[rows]="tableStore.rows()"
|
[rows]="tableStore.rows()"
|
||||||
[actions]="actions()"
|
[actions]="actions()"
|
||||||
[totalRecords]="tableStore.totalRecords()"
|
[totalRecords]="tableStore.filteredRecords()"
|
||||||
[pageIndex]="tableStore.queryState.pageIndex()"
|
[pageIndex]="tableStore.queryState.pageIndex()"
|
||||||
[pageSize]="tableStore.queryState.pageSize()"
|
[pageSize]="tableStore.queryState.pageSize()"
|
||||||
tableTitle="Tenants"
|
tableTitle="Tenants"
|
||||||
|
|||||||
@@ -84,7 +84,9 @@
|
|||||||
solid button ('Next') to outline style and remove its glow/box-shadow.
|
solid button ('Next') to outline style and remove its glow/box-shadow.
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
:host-context(.modern-modal-footer:has(.ti-btn-outline-primary:hover:not(:disabled))) button.ti-btn-primary-full:not(:hover),
|
:host-context(.modern-modal-footer:has(.ti-btn-outline-primary:hover:not(:disabled))) button.ti-btn-primary-full:not(:hover),
|
||||||
:host-context(.modern-modal-footer:has(.onboarding-save-btn:hover:not(:disabled))) button.ti-btn-primary-full:not(:hover) {
|
:host-context(.modern-modal-footer:has(.ti-btn-outline-primary:hover:not(:disabled))) button.ti-btn-success-full:not(:hover),
|
||||||
|
:host-context(.modern-modal-footer:has(.onboarding-save-btn:hover:not(:disabled))) button.ti-btn-primary-full:not(:hover),
|
||||||
|
:host-context(.modern-modal-footer:has(.onboarding-save-btn:hover:not(:disabled))) button.ti-btn-success-full:not(:hover) {
|
||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
color: var(--primary, #7c3deb) !important;
|
color: var(--primary, #7c3deb) !important;
|
||||||
border: 1.5px solid var(--primary, #7c3deb) !important;
|
border: 1.5px solid var(--primary, #7c3deb) !important;
|
||||||
|
|||||||
@@ -114,7 +114,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
} @else
|
} @else
|
||||||
{
|
{
|
||||||
@for (row of rows(); track row['id']; let rowIndex = $index) {
|
@for (row of rows(); track row['id'] ?? $index; let rowIndex = $index) {
|
||||||
<tr [class]="getRowClass(row, $index)" (click)="onRowClick(row)">
|
<tr [class]="getRowClass(row, $index)" (click)="onRowClick(row)">
|
||||||
@for (column of columns(); track column.key) {
|
@for (column of columns(); track column.key) {
|
||||||
<td [class]="getCellClass(column)">
|
<td [class]="getCellClass(column)">
|
||||||
|
|||||||
@@ -58,9 +58,8 @@ export class DataTableStore<TItem, TRow extends DataTableRecord = TItem & DataTa
|
|||||||
if (!response) return;
|
if (!response) return;
|
||||||
|
|
||||||
const currentQuery = this.queryState.getQuery();
|
const currentQuery = this.queryState.getQuery();
|
||||||
if (response.draw !== currentQuery.draw) return;
|
const rowsList = response.rows || [];
|
||||||
|
const mappedRows: TRow[] = rowsList.map((item, index) => {
|
||||||
const mappedRows: TRow[] = response.rows.map((item, index) => {
|
|
||||||
const serialNumber = (currentQuery.page - 1) * currentQuery.pageSize + index + 1;
|
const serialNumber = (currentQuery.page - 1) * currentQuery.pageSize + index + 1;
|
||||||
if (options.mapRow) {
|
if (options.mapRow) {
|
||||||
return options.mapRow(item, serialNumber, currentQuery);
|
return options.mapRow(item, serialNumber, currentQuery);
|
||||||
@@ -72,8 +71,8 @@ export class DataTableStore<TItem, TRow extends DataTableRecord = TItem & DataTa
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.rows.set(mappedRows);
|
this.rows.set(mappedRows);
|
||||||
this.totalRecords.set(response.total);
|
this.totalRecords.set(response.total ?? 0);
|
||||||
this.filteredRecords.set(response.filtered);
|
this.filteredRecords.set(response.filtered ?? response.total ?? 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.load(this.queryState.getQuery());
|
this.load(this.queryState.getQuery());
|
||||||
|
|||||||
Reference in New Issue
Block a user