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
b7474aa5
Commit
b7474aa5
authored
Sep 07, 2025
by
Ooh-Ao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
เชื่อมโยง
parent
9e7c3447
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
216 additions
and
47 deletions
+216
-47
m-menuitems-widget.service.ts
src/app/portal-manage/services/m-menuitems-widget.service.ts
+34
-44
dataset-widget-linker.component.scss
...ge/widget-management/dataset-widget-linker.component.scss
+113
-0
dataset-widget-linker.component.ts
...nage/widget-management/dataset-widget-linker.component.ts
+4
-3
widget-list.component.html
...ortal-manage/widget-management/widget-list.component.html
+5
-0
nav.service.ts
src/app/shared/services/nav.service.ts
+6
-0
ssss.ini
ssss.ini
+24
-0
เข้าใจแล้วครับ คุณต้องการระบบจัดการวิดเ.ini
เข้าใจแล้วครับ คุณต้องการระบบจัดการวิดเ.ini
+30
-0
No files found.
src/app/portal-manage/services/m-menuitems-widget.service.ts
View file @
b7474aa5
import
{
Injectable
}
from
'@angular/core'
;
import
{
Injectable
}
from
'@angular/core'
;
import
{
HttpClient
}
from
'@angular/common/http'
;
import
{
HttpClient
}
from
'@angular/common/http'
;
import
{
Observable
,
of
}
from
'rxjs'
;
import
{
Observable
,
of
}
from
'rxjs'
;
import
{
map
,
catchError
,
tap
}
from
'rxjs/operators'
;
import
{
map
,
catchError
}
from
'rxjs/operators'
;
// Removed tap
import
{
MenuItemsWidget
}
from
'../models/m-menuitems-widget.model'
;
import
{
MenuItemsWidget
}
from
'../models/m-menuitems-widget.model'
;
import
{
environment
}
from
'../../../environments/environment'
;
import
{
TokenService
}
from
'../../shared/services/token.service'
;
// Import TokenService
@
Injectable
({
@
Injectable
({
providedIn
:
'root'
providedIn
:
'root'
})
})
export
class
MMenuitemsWidgetService
{
export
class
MMenuitemsWidgetService
{
private
dataUrl
=
'assets/data/d-menuitems-widget.json'
;
private
baseUrl
=
environment
.
url
+
'mmenuitems-widget'
;
// Base API URL from user's provided endpoints
private
linkedWidgetsCache
:
MenuItemsWidget
[]
=
[];
// In-memory cache for simulation
constructor
(
private
http
:
HttpClient
)
{
}
constructor
(
private
http
:
HttpClient
,
private
tokenService
:
TokenService
)
{
}
// Inject TokenService
/**
/**
* Gets
all widget menu items and filters them by the provided datasetId
.
* Gets
linked widgets for a specific dataset from the backend API
.
*
In a real application, this filtering would ideally be done on the backend
.
*
Uses the /mmenuitems-widget/search endpoint
.
* @param datasetId The ID of the dataset to filter widgets for.
* @param datasetId The ID of the dataset to filter widgets for.
* @returns An Observable of MenuItemsWidget[] that are linked to the given datasetId.
* @returns An Observable of MenuItemsWidget[] that are linked to the given datasetId.
*/
*/
getWidgetsForDataset
(
datasetId
:
string
):
Observable
<
MenuItemsWidget
[]
>
{
getWidgetsForDataset
(
datasetId
:
string
):
Observable
<
MenuItemsWidget
[]
>
{
// Simulate fetching from backend or use cache if available
const
companyId
=
this
.
tokenService
.
getSelectCompany
().
companyId
;
// Get companyId from TokenService
if
(
this
.
linkedWidgetsCache
.
length
>
0
)
{
// Assuming the backend /search endpoint supports filtering by datasetId and companyId
return
of
(
this
.
linkedWidgetsCache
.
filter
(
item
=>
(
item
as
any
).
datasetId
===
datasetId
));
return
this
.
http
.
get
<
any
[]
>
(
`
${
this
.
baseUrl
}
/search`
,
{
params
:
{
datasetId
,
companyId
}
}).
pipe
(
}
else
{
map
(
items
=>
items
.
map
(
item
=>
new
MenuItemsWidget
(
item
))),
return
this
.
http
.
get
<
any
[]
>
(
this
.
dataUrl
).
pipe
(
catchError
(
error
=>
{
tap
(
items
=>
{
console
.
error
(
'Error loading linked widgets:'
,
error
);
// Populate cache on first load
return
of
([]);
// Return an empty array on error
this
.
linkedWidgetsCache
=
items
.
map
(
item
=>
new
MenuItemsWidget
(
item
));
})
}),
);
map
(
items
=>
{
const
filteredItems
=
items
.
filter
(
item
=>
item
.
datasetId
===
datasetId
);
return
filteredItems
.
map
(
item
=>
new
MenuItemsWidget
(
item
));
}),
catchError
(
error
=>
{
console
.
error
(
'Error loading widget menu items:'
,
error
);
return
of
([]);
// Return an empty array on error
})
);
}
}
}
/**
/**
* S
IMULATED: Saves a linked widget. In a real app, this would be a POST/PUT request
.
* S
aves a linked widget to the backend API
.
* U
pdates the in-memory cache and logs the action
.
* U
ses the POST /mmenuitems-widget endpoint for both creation and update
.
* @param menuItem The MenuItemsWidget to save.
* @param menuItem The MenuItemsWidget to save.
* @returns An Observable of the saved MenuItemsWidget.
* @returns An Observable of the saved MenuItemsWidget.
*/
*/
saveLinkedWidget
(
menuItem
:
MenuItemsWidget
):
Observable
<
MenuItemsWidget
>
{
saveLinkedWidget
(
menuItem
:
MenuItemsWidget
):
Observable
<
MenuItemsWidget
>
{
// Check if it already exists (for update scenario)
// Use POST for both creation and update as per provided API.
const
index
=
this
.
linkedWidgetsCache
.
findIndex
(
item
=>
item
.
itemId
===
menuItem
.
itemId
);
return
this
.
http
.
post
<
MenuItemsWidget
>
(
this
.
baseUrl
,
menuItem
).
pipe
(
if
(
index
>
-
1
)
{
catchError
(
error
=>
{
this
.
linkedWidgetsCache
[
index
]
=
menuItem
;
console
.
error
(
'Error saving linked widget:'
,
error
);
console
.
log
(
'Simulating update linked widget:'
,
menuItem
);
throw
error
;
// Re-throw to propagate error
}
else
{
})
this
.
linkedWidgetsCache
.
push
(
menuItem
);
);
console
.
log
(
'Simulating save new linked widget:'
,
menuItem
);
}
// In a real app, the backend would return the saved item.
return
of
(
menuItem
);
}
}
/**
/**
*
SIMULATED: Deletes a linked widget by its itemId. In a real app, this would be a DELETE request
.
*
Deletes a linked widget by its itemId from the backend API
.
* U
pdates the in-memory cache and logs the action
.
* U
ses the DELETE /mmenuitems-widget endpoint, passing itemId as a query parameter
.
* @param itemId The itemId of the MenuItemsWidget to delete.
* @param itemId The itemId of the MenuItemsWidget to delete.
* @returns An Observable indicating success.
* @returns An Observable indicating success.
*/
*/
deleteLinkedWidget
(
itemId
:
string
):
Observable
<
any
>
{
deleteLinkedWidget
(
itemId
:
string
):
Observable
<
any
>
{
this
.
linkedWidgetsCache
=
this
.
linkedWidgetsCache
.
filter
(
item
=>
item
.
itemId
!==
itemId
);
// Assuming DELETE /mmenuitems-widget expects itemId as a query parameter
console
.
log
(
'Simulating delete linked widget with ID:'
,
itemId
);
return
this
.
http
.
delete
<
any
>
(
this
.
baseUrl
,
{
params
:
{
itemId
}
}).
pipe
(
// In a real app, this would return a status or confirmation.
catchError
(
error
=>
{
return
of
({
success
:
true
});
console
.
error
(
'Error deleting linked widget:'
,
error
);
throw
error
;
// Re-throw to propagate error
})
);
}
}
}
}
src/app/portal-manage/widget-management/dataset-widget-linker.component.scss
View file @
b7474aa5
/* Styles for dataset-widget-linker.component.scss */
:host
{
display
:
block
;
background-color
:
#f9fafb
;
/* Light background for the whole component */
padding
:
1rem
;
/* Consistent padding around the component */
}
.p-4
{
padding
:
1rem
!
important
;
}
.sm
\
:p-6
{
@media
(
min-width
:
640px
)
{
padding
:
1
.5rem
!
important
;
}
}
.lg
\
:p-8
{
@media
(
min-width
:
1024px
)
{
padding
:
2rem
!
important
;
}
}
/* Styling for the select dropdown */
#dataset-select
{
border
:
1px
solid
#d1d5db
;
/* border-gray-300 */
border-radius
:
0
.375rem
;
/* rounded-md */
padding-left
:
0
.75rem
;
/* pl-3 */
padding-right
:
2
.5rem
;
/* pr-10 */
padding-top
:
0
.5rem
;
/* py-2 */
padding-bottom
:
0
.5rem
;
/* py-2 */
font-size
:
1rem
;
/* text-base */
line-height
:
1
.5rem
;
width
:
100%
;
box-shadow
:
0
1px
2px
0
rgba
(
0
,
0
,
0
,
0
.05
);
/* shadow-sm */
transition
:
border-color
0
.15s
ease-in-out
,
box-shadow
0
.15s
ease-in-out
;
&
:focus
{
outline
:
none
;
border-color
:
#6366f1
;
/* focus:border-indigo-500 */
box-shadow
:
0
0
0
3px
rgba
(
99
,
102
,
241
,
0
.25
);
/* focus:ring-indigo-500 */
}
}
/* Styling for the widget list containers */
.border
{
border
:
1px
solid
#e5e7eb
;
/* border-gray-200 */
}
.rounded-lg
{
border-radius
:
0
.5rem
;
}
.shadow-sm
{
box-shadow
:
0
1px
2px
0
rgba
(
0
,
0
,
0
,
0
.05
);
}
.divide-y
>
:not
([
hidden
])
~
:not
([
hidden
])
{
border-top-width
:
1px
;
border-color
:
#e5e7eb
;
/* divide-gray-200 */
}
/* Styling for list items */
ul
[
role
=
"list"
]
li
{
padding
:
1rem
;
/* p-4 */
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
background-color
:
#ffffff
;
/* bg-white */
transition
:
background-color
0
.15s
ease-in-out
;
&
:hover
{
background-color
:
#f9fafb
;
/* hover:bg-gray-50 */
}
}
/* Styling for buttons */
button
{
display
:
inline-flex
;
align-items
:
center
;
padding
:
0
.375rem
0
.75rem
;
/* px-3 py-1.5 */
border-width
:
1px
;
border-color
:
transparent
;
font-size
:
0
.75rem
;
/* text-xs */
font-weight
:
500
;
/* font-medium */
border-radius
:
0
.375rem
;
/* rounded-md */
box-shadow
:
0
1px
2px
0
rgba
(
0
,
0
,
0
,
0
.05
);
color
:
#ffffff
;
/* text-white */
transition
:
background-color
0
.15s
ease-in-out
,
box-shadow
0
.15s
ease-in-out
;
&
:focus
{
outline
:
none
;
box-shadow
:
0
0
0
3px
rgba
(
99
,
102
,
241
,
0
.25
);
/* focus:ring-indigo-500 */
box-shadow
:
0
0
0
3px
rgba
(
239
,
68
,
68
,
0
.25
);
/* focus:ring-red-500 */
box-shadow
:
0
0
0
3px
rgba
(
99
,
102
,
241
,
0
.25
)
,
0
0
0
3px
rgba
(
239
,
68
,
68
,
0
.25
);
/* Combined for both colors */
}
}
.bg-indigo-600
{
background-color
:
#4f46e5
;
&
:hover
{
background-color
:
#4338ca
;
/* hover:bg-indigo-700 */
}
}
.bg-red-600
{
background-color
:
#dc2626
;
&
:hover
{
background-color
:
#b91c1c
;
/* hover:bg-red-700 */
}
}
src/app/portal-manage/widget-management/dataset-widget-linker.component.ts
View file @
b7474aa5
...
@@ -89,10 +89,11 @@ export class DatasetWidgetLinkerComponent implements OnInit {
...
@@ -89,10 +89,11 @@ export class DatasetWidgetLinkerComponent implements OnInit {
dialogRef
.
afterClosed
().
subscribe
(
resultConfig
=>
{
dialogRef
.
afterClosed
().
subscribe
(
resultConfig
=>
{
if
(
resultConfig
)
{
if
(
resultConfig
)
{
const
newLinkedWidget
:
MenuItemsWidget
=
{
const
newLinkedWidget
:
MenuItemsWidget
=
{
companyId
:
'
1
'
,
// Placeholder, ideally from user context
companyId
:
'
DEMO
'
,
// Placeholder, ideally from user context
itemId
:
`link-
${
this
.
selectedDataset
!
.
itemId
}
-
${
widget
.
widgetId
}
`
,
itemId
:
this
.
selectedDataset
!
.
itemId
,
widget
:
widget
,
// The base widget definition
widget
:
widget
,
// The base widget definition
config
:
JSON
.
stringify
(
resultConfig
),
// Save configured object as string
// config: JSON.stringify(resultConfig), // Save configured object as string
config
:
''
,
data
:
''
,
// Not used for linking, but part of model
data
:
''
,
// Not used for linking, but part of model
perspective
:
''
// Not used for linking, but part of model
perspective
:
''
// Not used for linking, but part of model
};
};
...
...
src/app/portal-manage/widget-management/widget-list.component.html
View file @
b7474aa5
...
@@ -5,6 +5,11 @@
...
@@ -5,6 +5,11 @@
<p
class=
"mt-2 text-sm text-gray-700"
>
A list of all base widgets registered in the system, fetched from the central API.
</p>
<p
class=
"mt-2 text-sm text-gray-700"
>
A list of all base widgets registered in the system, fetched from the central API.
</p>
</div>
</div>
<!-- Add/Delete functionality removed as this now points to a real API -->
<!-- Add/Delete functionality removed as this now points to a real API -->
<div
class=
"mt-4 sm:mt-0 sm:ml-16 sm:flex-none"
>
<button
routerLink=
"/portal-manage/widget-management/linker"
type=
"button"
class=
"inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto"
>
Manage Dataset Widgets
</button>
</div>
</div>
</div>
<!-- Registered Widgets List -->
<!-- Registered Widgets List -->
...
...
src/app/shared/services/nav.service.ts
View file @
b7474aa5
...
@@ -108,6 +108,12 @@ export class NavService implements OnDestroy {
...
@@ -108,6 +108,12 @@ export class NavService implements OnDestroy {
title
:
'คลังวิดเจ็ต'
,
title
:
'คลังวิดเจ็ต'
,
type
:
'link'
type
:
'link'
},
},
{
path
:
`/portal-manage/widget-management/linker`
,
title
:
'เชื่อมโยงวิดเจ็ตกับชุดข้อมูล'
,
icon
:
'link'
,
type
:
'link'
},
]
]
};
};
}
}
...
...
ssss.ini
0 → 100644
View file @
b7474aa5
จุดเด่น:
*
สถาปัตยกรรมยอดเยี่ยม:
การออกแบบโดยใช้
DashboardStateService
เป็นศูนย์กลางในการจัดการข้อมูล
และมี
BaseWidgetComponent
เป็นคลาสพื้นฐานสำหรับวิดเจ็ตทั้งหมด
เป็นรูปแบบที่ทันสมัย
(Modern
Architecture)
ช่วยให้ประสิทธิภาพดี
ลดการเรียก
API
ซ้ำซ้อน
และง่ายต่อการบำรุงรักษาและเพิ่มวิดเจ็ตใหม่ๆ
ในอนาคต
*
การทำงานสมบูรณ์:
ระบบแดชบอร์ดมีฟังก์ชันการทำงานที่ครบถ้วน
ทั้งการสร้าง/ลบ/แก้ไขแดชบอร์ด,
การเพิ่ม/ลบ/ปรับขนาดวิดเจ็ต,
และการตั้งค่าวิดเจ็ตแต่ละตัวผ่านไดอะล็อก
ซึ่งทั้งหมดทำงานบน
ejs-dashboardlayout
ของ
Syncfusion
ได้อย่างลงตัว
*
ดีไซน์สวยงามและทันสมัย:
การใช้
Tailwind
CSS
ทำให้ได้
UI
ที่เป็น
Card-based
design
ดูสะอาดตาและทันสมัย
ในหน้าแสดงผล
(Viewer)
ได้มีการนำเส้นขอบและหัวข้อของ
Panel
ออก
ทำให้วิดเจ็ตต่างๆ
แสดงผลแบบไร้รอยต่อ
(Seamless)
ซึ่งสวยงามมากครับ
*
ประสบการณ์ใช้งานที่ดี
(UX):
วิดเจ็ตต่างๆ
มีสถานะ
Loading
และ
Error
แสดงผลชัดเจน
เช่น
KpiWidgetComponent
จะแสดง
Spinner
ขณะรอข้อมูล
และแสดงไอคอนพร้อมข้อความเมื่อมีข้อผิดพลาด
ซึ่งเป็นสิ่งที่ดีต่อผู้ใช้งาน
ข้อเสนอแนะเพื่อการปรับปรุง:
ผมมีข้อเสนอแนะเล็กน้อย
2-3
จุดที่สามารถปรับปรุงเพิ่มเติมได้ครับ
1.
เปิดการแจ้งเตือนเมื่อบันทึกสำเร็จ:
ในไฟล์
dashboard-management.component.ts
โค้ดที่ใช้แสดง
NotificationService
แจ้งเตือนผู้ใช้เมื่อบันทึก
Layout
หรือชื่อแดชบอร์ดสำเร็จ
ถูกคอมเมนต์ปิดไว้
การเปิดใช้งานส่วนนี้จะทำให้ผู้ใช้ได้รับ
Feedback
ที่ชัดเจนว่าการบันทึกสำเร็จแล้ว
2.
ลดความซ้ำซ้อนของโค้ด:
widgetComponentMap
ซึ่งทำหน้าที่จับคู่ชื่อคอมโพเนนต์กับคลาส
มีอยู่ทั้งใน
dashboard-management.component.ts
และ
dashboard-viewer.component.ts
เราสามารถย้าย
Map
นี้ไปไว้ในไฟล์ที่ใช้ร่วมกัน
(Shared
file/service)
เพื่อลดความซ้ำซ้อนและง่ายต่อการจัดการเมื่อมีวิดเจ็ตใหม่ๆ
เพิ่มเข้ามา
3.
ปรับปรุง
UX
ของหน้าแดชบอร์ดว่างๆ:
เมื่อผู้ใช้สร้างแดชบอร์ดใหม่
หน้าจอจะว่างเปล่า
เราอาจเพิ่มข้อความแนะนำ
เช่น
"ลากวิดเจ็ตจากแถบด้านข้างมาวางที่นี่"
เพื่อแนะนำผู้ใช้ในขั้นตอนต่อไป
สรุปโดยรวม:
แดชบอร์ดของคุณทำงานได้อย่างสมบูรณ์
มีสถาปัตยกรรมที่แข็งแกร่ง
และดีไซน์ที่สวยงามทันสมัย
ข้อเสนอแนะของผมเป็นเพียงการปรับปรุงเล็กๆ
น้อยๆ
(Minor
refinements)
เพื่อให้ระบบสมบูรณ์แบบยิ่งขึ้นครับ
ผมสามารถช่วยคุณในการปรับปรุงข้อ
1
และ
2
ได้เลย
หากคุณต้องการ
ให้ผมเริ่มจากการเปิดใช้งานการแจ้งเตือนเมื่อบันทึกสำเร็จก่อนเลยไหมครับ?
เข้าใจแล้วครับ คุณต้องการระบบจัดการวิดเ.ini
0 → 100644
View file @
b7474aa5
เข้าใจแล้วครับ
คุณต้องการระบบจัดการวิดเจ็ต
2
ส่วนหลักๆ
คือ:
เข้าใจแล้วครับ
คุณต้องการระบบจัดการวิดเจ็ต
2
ส่วนหลักๆ
คือ:
1.
หน้าลงทะเบียนวิดเจ็ตกลาง
(Global
Widget
Registration):
*
เป็นหน้ารวมสำหรับลงทะเบียนวิดเจ็ตพื้นฐานทั้งหมดที่มีในระบบ
(Master
List)
*
มีฟังก์ชันสำหรับดูตัวอย่าง
(Preview)
วิดเจ็ตได้
*
ใช้คอมโพเนนต์
widget-list.component.html
2.
หน้าจัดการวิดเจ็ตสำหรับแต่ละ
Dataset:
*
เป็นหน้าที่ใช้เชื่อมโยงวิดเจ็ตจาก
Master
List
(ข้อ
1)
เข้ากับ
Dataset
ที่เลือก
*
สามารถกำหนดค่าเริ่มต้น
(Default
Config)
ของวิดเจ็ตสำหรับ
Dataset
นั้นๆ
ได้โดยเฉพาะ
*
ข้อมูลที่จัดการในหน้านี้คือข้อมูลที่
m-menuitems-widget.service.ts
ที่เราเพิ่งสร้างไป
จะนำไปใช้งาน
นี่เป็นฟีเจอร์ใหญ่
ผมจะวางแผนการทำงานดังนี้:
1.
สร้างคอมโพเนนต์ใหม่:
*
widget-list.component.ts
และ
.html
สำหรับหน้าลงทะเบียนวิดเจ็ตกลาง
*
dataset-widget-linker.component.ts
และ
.html
สำหรับหน้าเชื่อมโยงวิดเจ็ตกับ
Dataset
2.
เพิ่ม
Route:
เพิ่มเส้นทางใน
portal-manage.routes.ts
เพื่อเข้าถึงหน้าจัดการวิดเจ็ตใหม่
3.
จำลอง
Backend:
*
สร้าง
master-widgets.json
เพื่อจำลองฐานข้อมูลของวิดเจ็ตที่ลงทะเบียนแล้ว
*
ปรับปรุง
MMenuitemsWidgetService
ให้สามารถ
"บันทึก"
ข้อมูลกลับไปยัง
d-menuitems-widget.json
ได้
4.
พัฒนา
Logic
ของคอมโพเนนต์:
*
`widget-list`:
แสดงรายการวิดเจ็ต,
เพิ่มวิดเจ็ตใหม่เข้าสู่ระบบ
(บันทึกลง
master-widgets.json)
*
`dataset-widget-linker`:
เลือก
Dataset,
แสดงรายการวิดเจ็ตที่ลงทะเบียนแล้วและวิดเจ็ตที่เชื่อมกับ
Dataset,
มี
UI
สำหรับการเชื่อมโยงและตั้งค่าเริ่มต้น
ผมจะเริ่มจากส่วนที่
1
คือ
การสร้างหน้าลงทะเบียนวิดเจ็ตกลาง
(`widget-list.component`)
ก่อนครับ
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