Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
portal-apps-manage
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
angular
portal-apps-manage
Commits
71f98397
Commit
71f98397
authored
Sep 07, 2025
by
Ooh-Ao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
widgets
parent
ddbcfa1f
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
325 additions
and
128 deletions
+325
-128
widget-config.component.html
...ard-management/widget-config/widget-config.component.html
+35
-2
widget-config-generator.service.ts
...portal-manage/services/widget-config-generator.service.ts
+28
-0
attendance-overview-widget.component.html
...-manage/widgets/attendance-overview-widget.component.html
+49
-0
attendance-overview-widget.component.ts
...al-manage/widgets/attendance-overview-widget.component.ts
+1
-24
employee-directory-widget.component.html
...l-manage/widgets/employee-directory-widget.component.html
+47
-0
employee-directory-widget.component.ts
...tal-manage/widgets/employee-directory-widget.component.ts
+13
-30
headcount-widget.component.html
...app/portal-manage/widgets/headcount-widget.component.html
+41
-0
headcount-widget.component.ts
src/app/portal-manage/widgets/headcount-widget.component.ts
+27
-31
payroll-summary-widget.component.html
...rtal-manage/widgets/payroll-summary-widget.component.html
+42
-0
payroll-summary-widget.component.ts
...portal-manage/widgets/payroll-summary-widget.component.ts
+1
-23
quick-links-widget.component.html
...gets/quick-links-widget/quick-links-widget.component.html
+1
-1
quick-links-widget.component.ts
...idgets/quick-links-widget/quick-links-widget.component.ts
+6
-5
welcome-widget.component.html
...nage/widgets/welcome-widget/welcome-widget.component.html
+19
-8
welcome-widget.component.ts
...manage/widgets/welcome-widget/welcome-widget.component.ts
+15
-4
No files found.
src/app/portal-manage/dashboard-management/widget-config/widget-config.component.html
View file @
71f98397
...
...
@@ -75,6 +75,12 @@
<mat-option
*
ngFor=
"let col of availableColumns"
[
value
]="
col
"
>
{{ col }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field
appearance=
"fill"
>
<mat-label>
Photo URL Field (Optional)
</mat-label>
<mat-select
[(
ngModel
)]="
currentConfig
.
photoField
"
name=
"photoField"
>
<mat-option
*
ngFor=
"let col of availableColumns"
[
value
]="
col
"
>
{{ col }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div
*
ngIf=
"widgetType === 'HeadcountWidgetComponent'"
>
...
...
@@ -88,6 +94,13 @@
<mat-option
*
ngFor=
"let col of availableColumns"
[
value
]="
col
"
>
{{ col }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field
appearance=
"fill"
>
<mat-label>
Chart Type
</mat-label>
<mat-select
[(
ngModel
)]="
currentConfig
.
chartType
"
name=
"chartType"
>
<mat-option
value=
"bar"
>
Bar Chart
</mat-option>
<mat-option
value=
"doughnut"
>
Doughnut Chart
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div
*
ngIf=
"widgetType === 'PayrollSummaryWidgetComponent'"
>
...
...
@@ -135,11 +148,25 @@
</div>
<div
*
ngIf=
"widgetType === 'WelcomeWidgetComponent'"
>
<mat-form-field
appearance=
"fill"
>
<mat-form-field
appearance=
"fill"
class=
"w-full"
>
<mat-label>
Title
</mat-label>
<input
matInput
[(
ngModel
)]="
currentConfig
.
title
"
name=
"title"
>
</mat-form-field>
<mat-form-field
appearance=
"fill"
>
<mat-form-field
appearance=
"fill"
class=
"w-full"
>
<mat-label>
Message Source
</mat-label>
<mat-select
[(
ngModel
)]="
currentConfig
.
messageType
"
name=
"messageType"
>
<mat-option
value=
"static"
>
Static Text
</mat-option>
<mat-option
value=
"dynamic"
>
From Data Field
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field
*
ngIf=
"currentConfig.messageType === 'static'"
appearance=
"fill"
class=
"w-full"
>
<mat-label>
Static Message
</mat-label>
<input
matInput
[(
ngModel
)]="
currentConfig
.
staticMessage
"
name=
"staticMessage"
>
</mat-form-field>
<mat-form-field
*
ngIf=
"currentConfig.messageType === 'dynamic'"
appearance=
"fill"
class=
"w-full"
>
<mat-label>
Message Field
</mat-label>
<mat-select
[(
ngModel
)]="
currentConfig
.
messageField
"
name=
"messageField"
>
<mat-option
*
ngFor=
"let col of availableColumns"
[
value
]="
col
"
>
{{ col }}
</mat-option>
...
...
@@ -196,6 +223,12 @@
<mat-option
*
ngFor=
"let col of availableColumns"
[
value
]="
col
"
>
{{ col }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field
appearance=
"fill"
>
<mat-label>
Icon Field (Optional)
</mat-label>
<mat-select
[(
ngModel
)]="
currentConfig
.
iconField
"
name=
"iconField"
>
<mat-option
*
ngFor=
"let col of availableColumns"
[
value
]="
col
"
>
{{ col }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<!-- Unified Column Editor for Grid-like Widgets -->
...
...
src/app/portal-manage/services/widget-config-generator.service.ts
View file @
71f98397
...
...
@@ -61,6 +61,34 @@ export class WidgetConfigGeneratorService {
newConfig
.
title
=
widget
.
thName
;
newConfig
.
optionsField
=
col1
;
break
;
case
'WelcomeWidgetComponent'
:
newConfig
.
title
=
widget
.
thName
;
newConfig
.
messageType
=
'static'
;
newConfig
.
staticMessage
=
'Welcome!'
;
newConfig
.
messageField
=
col1
;
break
;
case
'QuickLinksWidgetComponent'
:
newConfig
.
title
=
widget
.
thName
;
newConfig
.
nameField
=
col1
;
newConfig
.
urlField
=
col2
;
newConfig
.
iconField
=
'icon'
;
// Assuming a column named 'icon' might exist
break
;
case
'HeadcountWidgetComponent'
:
newConfig
.
title
=
widget
.
thName
;
newConfig
.
categoryField
=
col1
;
newConfig
.
chartType
=
'bar'
;
break
;
case
'EmployeeDirectoryWidgetComponent'
:
newConfig
.
title
=
widget
.
thName
;
newConfig
.
nameField
=
col1
;
newConfig
.
positionField
=
col2
;
newConfig
.
departmentField
=
columns
.
length
>
2
?
columns
[
2
]
:
col1
;
newConfig
.
photoField
=
'photoUrl'
;
break
;
default
:
newConfig
.
title
=
widget
.
thName
;
...
...
src/app/portal-manage/widgets/attendance-overview-widget.component.html
0 → 100644
View file @
71f98397
<div
class=
"bg-white rounded-xl shadow-lg flex flex-col h-full"
>
<!-- Header -->
<div
class=
"flex-shrink-0 p-4 sm:p-6 border-b border-gray-200"
>
<h4
class=
"text-md sm:text-lg font-semibold text-gray-600 truncate"
>
{{ title }}
</h4>
</div>
<!-- Body -->
<div
class=
"flex-grow p-4 sm:p-6 overflow-auto"
>
<!-- Loading State -->
<div
*
ngIf=
"isLoading"
class=
"flex justify-center items-center h-full"
>
<div
class=
"animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"
></div>
</div>
<!-- Error State -->
<div
*
ngIf=
"hasError"
class=
"flex flex-col justify-center items-center h-full text-red-500"
>
<i
class=
"bi bi-exclamation-circle-fill text-4xl"
></i>
<p
class=
"mt-2 font-semibold"
>
{{ errorMessage }}
</p>
</div>
<!-- Content -->
<div
*
ngIf=
"!isLoading && !hasError"
class=
"grid grid-cols-1 sm:grid-cols-3 gap-4 h-full"
>
<!-- Present -->
<div
class=
"flex flex-col items-center justify-center p-4 bg-green-50 rounded-lg"
>
<i
class=
"bi bi-person-check-fill text-3xl text-green-500"
></i>
<p
class=
"mt-2 text-3xl font-bold text-green-800"
>
{{ present }}
</p>
<p
class=
"text-sm font-medium text-green-600"
>
Present
</p>
</div>
<!-- On Leave -->
<div
class=
"flex flex-col items-center justify-center p-4 bg-yellow-50 rounded-lg"
>
<i
class=
"bi bi-person-dash-fill text-3xl text-yellow-500"
></i>
<p
class=
"mt-2 text-3xl font-bold text-yellow-800"
>
{{ onLeave }}
</p>
<p
class=
"text-sm font-medium text-yellow-600"
>
On Leave
</p>
</div>
<!-- Absent -->
<div
class=
"flex flex-col items-center justify-center p-4 bg-red-50 rounded-lg"
>
<i
class=
"bi bi-person-x-fill text-3xl text-red-500"
></i>
<p
class=
"mt-2 text-3xl font-bold text-red-800"
>
{{ absent }}
</p>
<p
class=
"text-sm font-medium text-red-600"
>
Absent
</p>
</div>
</div>
</div>
</div>
src/app/portal-manage/widgets/attendance-overview-widget.component.ts
View file @
71f98397
...
...
@@ -8,30 +8,7 @@ import { BaseWidgetComponent } from './base-widget.component';
selector
:
'app-attendance-overview-widget'
,
standalone
:
true
,
imports
:
[
CommonModule
],
template
:
`
<div class="p-4 h-full bg-yellow-50">
<h3 class="font-bold text-yellow-800">{{ title }}</h3>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-yellow-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p class="text-sm text-yellow-600">Present: {{ present }}</p>
<p class="text-sm text-yellow-600">On Leave: {{ onLeave }}</p>
<p class="text-sm text-yellow-600">Absent: {{ absent }}</p>
</div>
</div>
`
templateUrl
:
'./attendance-overview-widget.component.html'
})
export
class
AttendanceOverviewWidgetComponent
extends
BaseWidgetComponent
{
public
present
:
number
=
0
;
...
...
src/app/portal-manage/widgets/employee-directory-widget.component.html
0 → 100644
View file @
71f98397
<div
class=
"bg-white rounded-xl shadow-lg flex flex-col h-full"
>
<!-- Header -->
<div
class=
"flex-shrink-0 p-4 sm:p-6 border-b border-gray-200"
>
<h4
class=
"text-md sm:text-lg font-semibold text-gray-600 truncate"
>
{{ title }}
</h4>
</div>
<!-- Body -->
<div
class=
"flex-grow p-4 sm:p-6 overflow-auto"
>
<!-- Loading State -->
<div
*
ngIf=
"isLoading"
class=
"flex justify-center items-center h-full"
>
<div
class=
"animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"
></div>
</div>
<!-- Error State -->
<div
*
ngIf=
"hasError"
class=
"flex flex-col justify-center items-center h-full text-red-500"
>
<i
class=
"bi bi-exclamation-circle-fill text-4xl"
></i>
<p
class=
"mt-2 font-semibold"
>
{{ errorMessage }}
</p>
</div>
<!-- Content -->
<div
*
ngIf=
"!isLoading && !hasError"
class=
"space-y-4"
>
<div
*
ngFor=
"let employee of employees"
class=
"flex items-center p-3 -m-3 rounded-lg hover:bg-gray-50 transition ease-in-out duration-150"
>
<div
class=
"flex-shrink-0"
>
<img
*
ngIf=
"employee.photoUrl"
[
src
]="
employee
.
photoUrl
"
alt=
""
class=
"h-12 w-12 rounded-full object-cover"
>
<div
*
ngIf=
"!employee.photoUrl"
class=
"h-12 w-12 rounded-full bg-gray-200 flex items-center justify-center"
>
<i
class=
"bi bi-person-fill text-2xl text-gray-500"
></i>
</div>
</div>
<div
class=
"ml-4"
>
<p
class=
"text-sm font-medium text-gray-900"
>
{{ employee.name }}
</p>
<p
class=
"text-sm text-gray-500"
>
{{ employee.position }}
</p>
<p
class=
"text-xs text-gray-400"
>
{{ employee.department }}
</p>
</div>
</div>
<div
*
ngIf=
"employees.length === 0"
class=
"text-center text-gray-400 py-8"
>
<i
class=
"bi bi-people text-4xl"
></i>
<p
class=
"mt-2"
>
No employees to display.
</p>
</div>
</div>
</div>
</div>
src/app/portal-manage/widgets/employee-directory-widget.component.ts
View file @
71f98397
...
...
@@ -4,40 +4,21 @@ import { CommonModule } from '@angular/common';
import
{
DashboardStateService
}
from
'../services/dashboard-state.service'
;
import
{
BaseWidgetComponent
}
from
'./base-widget.component'
;
export
interface
Employee
{
name
:
string
;
position
:
string
;
department
:
string
;
photoUrl
?:
string
;
}
@
Component
({
selector
:
'app-employee-directory-widget'
,
standalone
:
true
,
imports
:
[
CommonModule
],
template
:
`
<div class="p-4 h-full bg-indigo-50">
<h3 class="font-bold text-indigo-800">{{ title }}</h3>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-indigo-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<div *ngIf="employees.length > 0">
<div *ngFor="let employee of employees" class="mb-2">
<p class="text-sm text-indigo-600">{{ employee.name }} - {{ employee.position }} ({{ employee.department }})</p>
</div>
</div>
<p *ngIf="employees.length === 0" class="text-sm text-indigo-600">No employees found.</p>
</div>
</div>
`
templateUrl
:
'./employee-directory-widget.component.html'
})
export
class
EmployeeDirectoryWidgetComponent
extends
BaseWidgetComponent
{
public
employees
:
any
[]
=
[];
public
employees
:
Employee
[]
=
[];
constructor
(
protected
override
dashboardStateService
:
DashboardStateService
)
{
super
(
dashboardStateService
);
...
...
@@ -53,14 +34,16 @@ export class EmployeeDirectoryWidgetComponent extends BaseWidgetComponent {
name
:
item
[
this
.
config
.
nameField
]
||
''
,
position
:
item
[
this
.
config
.
positionField
]
||
''
,
department
:
item
[
this
.
config
.
departmentField
]
||
''
,
photoUrl
:
this
.
config
.
photoField
?
item
[
this
.
config
.
photoField
]
:
undefined
}));
}
onReset
():
void
{
this
.
title
=
'Employee Directory (Default)'
;
this
.
employees
=
[
{
name
:
'John Doe'
,
position
:
'Software Engineer'
,
department
:
'IT'
},
{
name
:
'Jane Smith'
,
position
:
'Project Manager'
,
department
:
'Operations'
},
{
name
:
'John Doe'
,
position
:
'Software Engineer'
,
department
:
'IT'
,
photoUrl
:
'https://randomuser.me/api/portraits/men/1.jpg'
},
{
name
:
'Jane Smith'
,
position
:
'Project Manager'
,
department
:
'Operations'
,
photoUrl
:
'https://randomuser.me/api/portraits/women/2.jpg'
},
{
name
:
'Peter Jones'
,
position
:
'UX Designer'
,
department
:
'Design'
,
photoUrl
:
'https://randomuser.me/api/portraits/men/3.jpg'
},
];
}
}
src/app/portal-manage/widgets/headcount-widget.component.html
0 → 100644
View file @
71f98397
<div
class=
"bg-white rounded-xl shadow-lg flex flex-col h-full"
>
<!-- Header -->
<div
class=
"flex-shrink-0 p-4 sm:p-6 border-b border-gray-200"
>
<h4
class=
"text-md sm:text-lg font-semibold text-gray-600 truncate"
>
{{ title }}
</h4>
<p
class=
"text-sm text-gray-500"
>
Total: {{ totalHeadcount }}
</p>
</div>
<!-- Body -->
<div
class=
"flex-grow p-2 sm:p-4 overflow-auto"
>
<!-- Loading State -->
<div
*
ngIf=
"isLoading"
class=
"flex justify-center items-center h-full"
>
<div
class=
"animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"
></div>
</div>
<!-- Error State -->
<div
*
ngIf=
"hasError"
class=
"flex flex-col justify-center items-center h-full text-red-500"
>
<i
class=
"bi bi-exclamation-circle-fill text-4xl"
></i>
<p
class=
"mt-2 font-semibold"
>
{{ errorMessage }}
</p>
</div>
<!-- Content -->
<div
*
ngIf=
"!isLoading && !hasError"
class=
"h-full"
>
<!-- Bar Chart -->
<ejs-chart
*
ngIf=
"config.chartType === 'bar'"
[
primaryXAxis
]="
primaryXAxis
"
[
primaryYAxis
]="
primaryYAxis
"
[
tooltip
]="
tooltip
"
height=
"100%"
>
<e-series-collection>
<e-series
[
dataSource
]="
breakdown
"
type=
"Bar"
xName=
"category"
yName=
"count"
name=
"Headcount"
></e-series>
</e-series-collection>
</ejs-chart>
<!-- Doughnut Chart -->
<ejs-accumulationchart
*
ngIf=
"config.chartType === 'doughnut'"
[
legendSettings
]="
legendSettings
"
[
tooltip
]="
tooltip
"
height=
"100%"
>
<e-accumulation-series-collection>
<e-accumulation-series
[
dataSource
]="
breakdown
"
xName=
"category"
yName=
"count"
innerRadius=
"40%"
[
dataLabel
]="
dataLabel
"
></e-accumulation-series>
</e-accumulation-series-collection>
</ejs-accumulationchart>
</div>
</div>
</div>
src/app/portal-manage/widgets/headcount-widget.component.ts
View file @
71f98397
import
{
Component
}
from
'@angular/core'
;
import
{
CommonModule
}
from
'@angular/common'
;
import
{
ChartAllModule
,
AccumulationChartAllModule
}
from
'@syncfusion/ej2-angular-charts'
;
import
{
DashboardStateService
}
from
'../services/dashboard-state.service'
;
import
{
BaseWidgetComponent
}
from
'./base-widget.component'
;
@
Component
({
selector
:
'app-headcount-widget'
,
standalone
:
true
,
imports
:
[
CommonModule
],
template
:
`
<div class="p-4 h-full bg-green-50">
<h3 class="font-bold text-green-800">{{ title }}</h3>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-green-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p class="text-sm text-green-600">Total Headcount: {{ totalHeadcount }}</p>
<div *ngIf="breakdown.length > 0">
<p class="text-sm text-green-600">By {{ config?.categoryField || 'Category' }}:</p>
<ul>
<li *ngFor="let item of breakdown" class="text-xs text-green-600">
{{ item.category }}: {{ item.count }}
</li>
</ul>
</div>
</div>
</div>
`
imports
:
[
CommonModule
,
ChartAllModule
,
AccumulationChartAllModule
],
templateUrl
:
'./headcount-widget.component.html'
})
export
class
HeadcountWidgetComponent
extends
BaseWidgetComponent
{
public
totalHeadcount
:
number
=
0
;
public
breakdown
:
{
category
:
string
,
count
:
number
}[]
=
[];
// Chart properties
public
primaryXAxis
:
Object
;
public
primaryYAxis
:
Object
;
public
legendSettings
:
Object
;
public
dataLabel
:
Object
;
public
tooltip
:
Object
;
constructor
(
protected
override
dashboardStateService
:
DashboardStateService
)
{
super
(
dashboardStateService
);
}
...
...
@@ -51,6 +30,23 @@ export class HeadcountWidgetComponent extends BaseWidgetComponent {
this
.
title
=
this
.
config
.
title
||
'Headcount'
;
this
.
totalHeadcount
=
0
;
this
.
breakdown
=
[];
// Initialize chart settings
this
.
primaryXAxis
=
{
valueType
:
'Category'
,
majorGridLines
:
{
width
:
0
}
};
this
.
primaryYAxis
=
{
majorTickLines
:
{
width
:
0
},
lineStyle
:
{
width
:
0
},
labelStyle
:
{
color
:
'transparent'
}
};
this
.
legendSettings
=
{
visible
:
true
,
position
:
'Bottom'
};
this
.
dataLabel
=
{
visible
:
true
,
name
:
'count'
,
position
:
'Inside'
};
this
.
tooltip
=
{
enable
:
true
};
}
onDataUpdate
(
data
:
any
[]):
void
{
...
...
src/app/portal-manage/widgets/payroll-summary-widget.component.html
0 → 100644
View file @
71f98397
<div
class=
"bg-white rounded-xl shadow-lg flex flex-col h-full"
>
<!-- Header -->
<div
class=
"flex-shrink-0 p-4 sm:p-6 border-b border-gray-200"
>
<h4
class=
"text-md sm:text-lg font-semibold text-gray-600 truncate"
>
{{ title }}
</h4>
</div>
<!-- Body -->
<div
class=
"flex-grow p-4 sm:p-6 overflow-auto"
>
<!-- Loading State -->
<div
*
ngIf=
"isLoading"
class=
"flex justify-center items-center h-full"
>
<div
class=
"animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"
></div>
</div>
<!-- Error State -->
<div
*
ngIf=
"hasError"
class=
"flex flex-col justify-center items-center h-full text-red-500"
>
<i
class=
"bi bi-exclamation-circle-fill text-4xl"
></i>
<p
class=
"mt-2 font-semibold"
>
{{ errorMessage }}
</p>
</div>
<!-- Content -->
<div
*
ngIf=
"!isLoading && !hasError"
class=
"grid grid-cols-1 sm:grid-cols-2 gap-4 h-full"
>
<!-- Total Payroll -->
<div
class=
"flex flex-col items-center justify-center p-4 bg-blue-50 rounded-lg"
>
<i
class=
"bi bi-cash-stack text-3xl text-blue-500"
></i>
<p
class=
"mt-2 text-3xl font-bold text-blue-800"
>
{{ totalPayroll | currency:'USD':'symbol':'1.0-0' }}
</p>
<p
class=
"text-sm font-medium text-blue-600"
>
Total Payroll
</p>
</div>
<!-- Employees Paid -->
<div
class=
"flex flex-col items-center justify-center p-4 bg-purple-50 rounded-lg"
>
<i
class=
"bi bi-people-fill text-3xl text-purple-500"
></i>
<p
class=
"mt-2 text-3xl font-bold text-purple-800"
>
{{ employeesPaid }}
</p>
<p
class=
"text-sm font-medium text-purple-600"
>
Employees Paid
</p>
</div>
</div>
</div>
</div>
src/app/portal-manage/widgets/payroll-summary-widget.component.ts
View file @
71f98397
...
...
@@ -8,29 +8,7 @@ import { BaseWidgetComponent } from './base-widget.component';
selector
:
'app-payroll-summary-widget'
,
standalone
:
true
,
imports
:
[
CommonModule
],
template
:
`
<div class="p-4 h-full bg-red-50">
<h3 class="font-bold text-red-800">{{ title }}</h3>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-red-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p class="text-sm text-red-600">Total Payroll: {{ totalPayroll | currency }}</p>
<p class="text-sm text-red-600">Employees Paid: {{ employeesPaid }}</p>
</div>
</div>
`
templateUrl
:
'./payroll-summary-widget.component.html'
})
export
class
PayrollSummaryWidgetComponent
extends
BaseWidgetComponent
{
public
totalPayroll
:
number
=
0
;
...
...
src/app/portal-manage/widgets/quick-links-widget/quick-links-widget.component.html
View file @
71f98397
...
...
@@ -23,7 +23,7 @@
<ul
*
ngIf=
"!isLoading && !hasError"
class=
"space-y-3"
>
<li
*
ngFor=
"let link of quickLinks"
>
<a
[
href
]="
link
.
url
"
target=
"_blank"
class=
"flex items-center p-3 -m-3 rounded-lg hover:bg-gray-50 transition ease-in-out duration-150"
>
<i
class=
"bi bi-link-45deg text-blue-500 text-xl
"
></i>
<i
[
class
]="'
bi
bi-
'
+
(
link
.
icon
||
'
link-45deg
')
+
'
text-blue-500
text-xl
'
"
></i>
<span
class=
"ml-3 font-medium text-gray-700 hover:text-blue-600"
>
{{ link.name }}
</span>
</a>
</li>
...
...
src/app/portal-manage/widgets/quick-links-widget/quick-links-widget.component.ts
View file @
71f98397
...
...
@@ -10,7 +10,7 @@ import { BaseWidgetComponent } from '../base-widget.component';
templateUrl
:
'./quick-links-widget.component.html'
,
})
export
class
QuickLinksWidgetComponent
extends
BaseWidgetComponent
{
public
quickLinks
:
{
name
:
string
,
url
:
string
}[]
=
[];
public
quickLinks
:
{
name
:
string
,
url
:
string
,
icon
?:
string
}[]
=
[];
constructor
(
protected
override
dashboardStateService
:
DashboardStateService
)
{
super
(
dashboardStateService
);
...
...
@@ -25,7 +25,8 @@ export class QuickLinksWidgetComponent extends BaseWidgetComponent {
if
(
this
.
config
.
nameField
&&
this
.
config
.
urlField
)
{
this
.
quickLinks
=
data
.
map
(
item
=>
({
name
:
item
[
this
.
config
.
nameField
],
url
:
item
[
this
.
config
.
urlField
]
url
:
item
[
this
.
config
.
urlField
],
icon
:
this
.
config
.
iconField
?
item
[
this
.
config
.
iconField
]
:
'link-45deg'
}));
}
}
...
...
@@ -33,9 +34,9 @@ export class QuickLinksWidgetComponent extends BaseWidgetComponent {
onReset
():
void
{
this
.
title
=
'Quick Links (Default)'
;
this
.
quickLinks
=
[
{
name
:
'Google'
,
url
:
'https://www.google.com'
},
{
name
:
'Angular'
,
url
:
'https://angular.io'
},
{
name
:
'Syncfusion'
,
url
:
'https://www.syncfusion.com'
},
{
name
:
'Google'
,
url
:
'https://www.google.com'
,
icon
:
'google'
},
{
name
:
'Angular'
,
url
:
'https://angular.io'
,
icon
:
'gem'
},
{
name
:
'Syncfusion'
,
url
:
'https://www.syncfusion.com'
,
icon
:
'code-square'
},
];
}
}
src/app/portal-manage/widgets/welcome-widget/welcome-widget.component.html
View file @
71f98397
<div
class=
"card h-100"
>
<div
class=
"card-body flex flex-col justify-center items-center"
>
<div
class=
"bg-white rounded-xl shadow-lg p-4 sm:p-6 flex flex-col h-full"
>
<!-- Header -->
<div
class=
"flex-shrink-0 border-b border-gray-200 pb-4 mb-4"
*
ngIf=
"title"
>
<h4
class=
"text-md sm:text-lg font-semibold text-gray-600 truncate"
>
{{ title }}
</h4>
</div>
<!-- Body -->
<div
class=
"flex-grow flex justify-center items-center"
>
<!-- Loading State -->
<div
*
ngIf=
"isLoading"
class=
"flex justify-center items-center h-full"
>
<div
class=
"animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"
></div>
<div
*
ngIf=
"isLoading"
class=
"text-center"
>
<div
class=
"animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto"
></div>
<p
class=
"text-gray-400 mt-2"
>
Loading...
</p>
</div>
<!-- Error State -->
<div
*
ngIf=
"hasError"
class=
"flex flex-col justify-center items-center h-full text-red-500"
>
<i
class=
"bi bi-exclamation-triangle-fill text-4xl"
></i>
<p
class=
"mt-2"
>
{{ errorMessage }}
</p>
<div
*
ngIf=
"hasError"
class=
"text-center text-red-500"
>
<i
class=
"bi bi-exclamation-circle-fill text-4xl"
></i>
<p
class=
"mt-2 font-semibold"
>
Error
</p>
<p
class=
"text-sm text-red-400"
>
{{ errorMessage }}
</p>
</div>
<!-- Content -->
<h3
*
ngIf=
"!isLoading && !hasError"
class=
"text-2xl font-bold text-gray-800"
>
{{ welcomeMessage }}
</h3>
<div
*
ngIf=
"!isLoading && !hasError"
class=
"text-center"
>
<h3
class=
"text-2xl font-bold text-gray-800"
>
{{ welcomeMessage }}
</h3>
</div>
</div>
</div>
src/app/portal-manage/widgets/welcome-widget/welcome-widget.component.ts
View file @
71f98397
...
...
@@ -17,13 +17,24 @@ export class WelcomeWidgetComponent extends BaseWidgetComponent {
}
applyInitialConfig
():
void
{
this
.
title
=
this
.
config
.
title
||
''
;
this
.
welcomeMessage
=
this
.
config
.
message
||
'...'
;
this
.
title
=
this
.
config
.
title
||
'Welcome'
;
if
(
this
.
config
.
messageType
===
'static'
)
{
this
.
welcomeMessage
=
this
.
config
.
staticMessage
||
'Welcome!'
;
}
else
{
this
.
welcomeMessage
=
'...'
;
// Placeholder while waiting for data
}
}
onDataUpdate
(
data
:
any
[]):
void
{
if
(
data
.
length
>
0
&&
this
.
config
.
messageField
)
{
this
.
welcomeMessage
=
data
[
0
][
this
.
config
.
messageField
];
if
(
this
.
config
.
messageType
===
'dynamic'
)
{
if
(
data
.
length
>
0
&&
this
.
config
.
messageField
)
{
this
.
welcomeMessage
=
data
[
0
][
this
.
config
.
messageField
];
}
else
{
this
.
welcomeMessage
=
'No message data'
;
}
}
else
{
// For static messages, the message is already set in applyInitialConfig
this
.
welcomeMessage
=
this
.
config
.
staticMessage
||
'Welcome!'
;
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment