mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-11 10:07:22 +00:00
feat: theme config service — REST API with versioning + backup
- ThemeService: read/write/validate theme.json, auto-backup on save - ThemeController: GET/PUT/POST /api/config/theme (public GET, versioned PUT) - ThemeConfig type with version + updatedAt fields - Default theme: Global Hospital blue scale - ConfigThemeModule registered in AppModule Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
50
data/theme-backups/theme-2026-04-02T09-33-40-460Z.json
Normal file
50
data/theme-backups/theme-2026-04-02T09-33-40-460Z.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"brand": {
|
||||
"name": "Helix Engage",
|
||||
"hospitalName": "Global Hospital",
|
||||
"logo": "/helix-logo.png",
|
||||
"favicon": "/favicon.ico"
|
||||
},
|
||||
"colors": {
|
||||
"brand": {
|
||||
"25": "rgb(239 246 255)",
|
||||
"50": "rgb(219 234 254)",
|
||||
"100": "rgb(191 219 254)",
|
||||
"200": "rgb(147 197 253)",
|
||||
"300": "rgb(96 165 250)",
|
||||
"400": "rgb(59 130 246)",
|
||||
"500": "rgb(37 99 235)",
|
||||
"600": "rgb(29 78 216)",
|
||||
"700": "rgb(30 64 175)",
|
||||
"800": "rgb(30 58 138)",
|
||||
"900": "rgb(23 37 84)",
|
||||
"950": "rgb(15 23 42)"
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"body": "'Satoshi', var(--font-inter, 'Inter'), -apple-system, 'Segoe UI', Roboto, Arial, sans-serif",
|
||||
"display": "'General Sans', var(--font-inter, 'Inter'), -apple-system, 'Segoe UI', Roboto, Arial, sans-serif"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in to Helix Engage",
|
||||
"subtitle": "Global Hospital",
|
||||
"showGoogleSignIn": true,
|
||||
"showForgotPassword": true,
|
||||
"poweredBy": {
|
||||
"label": "Powered by F0rty2.ai",
|
||||
"url": "https://f0rty2.ai"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Helix Engage",
|
||||
"subtitle": "Global Hospital · {role}"
|
||||
},
|
||||
"ai": {
|
||||
"quickActions": [
|
||||
{ "label": "Doctor availability", "prompt": "What doctors are available and what are their visiting hours?" },
|
||||
{ "label": "Clinic timings", "prompt": "What are the clinic locations and timings?" },
|
||||
{ "label": "Patient history", "prompt": "Can you summarize this patient's history?" },
|
||||
{ "label": "Treatment packages", "prompt": "What treatment packages are available?" }
|
||||
]
|
||||
}
|
||||
}
|
||||
62
data/theme-backups/theme-2026-04-02T09-34-04-404Z.json
Normal file
62
data/theme-backups/theme-2026-04-02T09-34-04-404Z.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"brand": {
|
||||
"name": "Test",
|
||||
"hospitalName": "Global Hospital",
|
||||
"logo": "/helix-logo.png",
|
||||
"favicon": "/favicon.ico"
|
||||
},
|
||||
"colors": {
|
||||
"brand": {
|
||||
"25": "rgb(239 246 255)",
|
||||
"50": "rgb(219 234 254)",
|
||||
"100": "rgb(191 219 254)",
|
||||
"200": "rgb(147 197 253)",
|
||||
"300": "rgb(96 165 250)",
|
||||
"400": "rgb(59 130 246)",
|
||||
"500": "rgb(37 99 235)",
|
||||
"600": "rgb(29 78 216)",
|
||||
"700": "rgb(30 64 175)",
|
||||
"800": "rgb(30 58 138)",
|
||||
"900": "rgb(23 37 84)",
|
||||
"950": "rgb(15 23 42)"
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"body": "'Satoshi', var(--font-inter, 'Inter'), -apple-system, 'Segoe UI', Roboto, Arial, sans-serif",
|
||||
"display": "'General Sans', var(--font-inter, 'Inter'), -apple-system, 'Segoe UI', Roboto, Arial, sans-serif"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in to Helix Engage",
|
||||
"subtitle": "Global Hospital",
|
||||
"showGoogleSignIn": true,
|
||||
"showForgotPassword": true,
|
||||
"poweredBy": {
|
||||
"label": "Powered by F0rty2.ai",
|
||||
"url": "https://f0rty2.ai"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Helix Engage",
|
||||
"subtitle": "Global Hospital · {role}"
|
||||
},
|
||||
"ai": {
|
||||
"quickActions": [
|
||||
{
|
||||
"label": "Doctor availability",
|
||||
"prompt": "What doctors are available and what are their visiting hours?"
|
||||
},
|
||||
{
|
||||
"label": "Clinic timings",
|
||||
"prompt": "What are the clinic locations and timings?"
|
||||
},
|
||||
{
|
||||
"label": "Patient history",
|
||||
"prompt": "Can you summarize this patient's history?"
|
||||
},
|
||||
{
|
||||
"label": "Treatment packages",
|
||||
"prompt": "What treatment packages are available?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
62
data/theme-backups/theme-2026-04-02T09-41-45-744Z.json
Normal file
62
data/theme-backups/theme-2026-04-02T09-41-45-744Z.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"brand": {
|
||||
"name": "Helix Engage",
|
||||
"hospitalName": "Global Hospital",
|
||||
"logo": "/helix-logo.png",
|
||||
"favicon": "/favicon.ico"
|
||||
},
|
||||
"colors": {
|
||||
"brand": {
|
||||
"25": "rgb(239 246 255)",
|
||||
"50": "rgb(219 234 254)",
|
||||
"100": "rgb(191 219 254)",
|
||||
"200": "rgb(147 197 253)",
|
||||
"300": "rgb(96 165 250)",
|
||||
"400": "rgb(59 130 246)",
|
||||
"500": "rgb(37 99 235)",
|
||||
"600": "rgb(29 78 216)",
|
||||
"700": "rgb(30 64 175)",
|
||||
"800": "rgb(30 58 138)",
|
||||
"900": "rgb(23 37 84)",
|
||||
"950": "rgb(15 23 42)"
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"body": "'Satoshi', var(--font-inter, 'Inter'), -apple-system, 'Segoe UI', Roboto, Arial, sans-serif",
|
||||
"display": "'General Sans', var(--font-inter, 'Inter'), -apple-system, 'Segoe UI', Roboto, Arial, sans-serif"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in to Helix Engage",
|
||||
"subtitle": "Global Hospital",
|
||||
"showGoogleSignIn": true,
|
||||
"showForgotPassword": true,
|
||||
"poweredBy": {
|
||||
"label": "Powered by F0rty2.ai",
|
||||
"url": "https://f0rty2.ai"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Helix Engage",
|
||||
"subtitle": "Global Hospital · {role}"
|
||||
},
|
||||
"ai": {
|
||||
"quickActions": [
|
||||
{
|
||||
"label": "Doctor availability",
|
||||
"prompt": "What doctors are available and what are their visiting hours?"
|
||||
},
|
||||
{
|
||||
"label": "Clinic timings",
|
||||
"prompt": "What are the clinic locations and timings?"
|
||||
},
|
||||
{
|
||||
"label": "Patient history",
|
||||
"prompt": "Can you summarize this patient's history?"
|
||||
},
|
||||
{
|
||||
"label": "Treatment packages",
|
||||
"prompt": "What treatment packages are available?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
62
data/theme-backups/theme-2026-04-02T09-42-24-047Z.json
Normal file
62
data/theme-backups/theme-2026-04-02T09-42-24-047Z.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"brand": {
|
||||
"name": "Helix Engage",
|
||||
"hospitalName": "Global Hospital",
|
||||
"logo": "/helix-logo.png",
|
||||
"favicon": "/favicon.ico"
|
||||
},
|
||||
"colors": {
|
||||
"brand": {
|
||||
"25": "rgb(250 245 255)",
|
||||
"50": "rgb(245 235 255)",
|
||||
"100": "rgb(235 215 254)",
|
||||
"200": "rgb(214 187 251)",
|
||||
"300": "rgb(182 146 246)",
|
||||
"400": "rgb(158 119 237)",
|
||||
"500": "rgb(127 86 217)",
|
||||
"600": "rgb(105 65 198)",
|
||||
"700": "rgb(83 56 158)",
|
||||
"800": "rgb(66 48 125)",
|
||||
"900": "rgb(53 40 100)",
|
||||
"950": "rgb(44 28 95)"
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"body": "'Satoshi', var(--font-inter, 'Inter'), -apple-system, 'Segoe UI', Roboto, Arial, sans-serif",
|
||||
"display": "'General Sans', var(--font-inter, 'Inter'), -apple-system, 'Segoe UI', Roboto, Arial, sans-serif"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in to Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital",
|
||||
"showGoogleSignIn": true,
|
||||
"showForgotPassword": true,
|
||||
"poweredBy": {
|
||||
"label": "Powered by F0rty2.ai",
|
||||
"url": "https://f0rty2.ai"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital · {role}"
|
||||
},
|
||||
"ai": {
|
||||
"quickActions": [
|
||||
{
|
||||
"label": "Doctor availability",
|
||||
"prompt": "What doctors are available and what are their visiting hours?"
|
||||
},
|
||||
{
|
||||
"label": "Clinic timings",
|
||||
"prompt": "What are the clinic locations and timings?"
|
||||
},
|
||||
{
|
||||
"label": "Patient history",
|
||||
"prompt": "Can you summarize this patient's history?"
|
||||
},
|
||||
{
|
||||
"label": "Treatment packages",
|
||||
"prompt": "What treatment packages are available?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
62
data/theme-backups/theme-2026-04-02T09-43-19-186Z.json
Normal file
62
data/theme-backups/theme-2026-04-02T09-43-19-186Z.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"brand": {
|
||||
"name": "Helix Engage",
|
||||
"hospitalName": "Global Hospital",
|
||||
"logo": "/helix-logo.png",
|
||||
"favicon": "/favicon.ico"
|
||||
},
|
||||
"colors": {
|
||||
"brand": {
|
||||
"25": "rgb(248 250 252)",
|
||||
"50": "rgb(241 245 249)",
|
||||
"100": "rgb(226 232 240)",
|
||||
"200": "rgb(203 213 225)",
|
||||
"300": "rgb(148 163 184)",
|
||||
"400": "rgb(100 116 139)",
|
||||
"500": "rgb(71 85 105)",
|
||||
"600": "rgb(47 64 89)",
|
||||
"700": "rgb(37 49 72)",
|
||||
"800": "rgb(30 41 59)",
|
||||
"900": "rgb(15 23 42)",
|
||||
"950": "rgb(2 6 23)"
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"body": "'Plus Jakarta Sans', 'Inter', sans-serif",
|
||||
"display": "'Plus Jakarta Sans', 'Inter', sans-serif"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in to Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital",
|
||||
"showGoogleSignIn": true,
|
||||
"showForgotPassword": true,
|
||||
"poweredBy": {
|
||||
"label": "Powered by F0rty2.ai",
|
||||
"url": "https://f0rty2.ai"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital · {role}"
|
||||
},
|
||||
"ai": {
|
||||
"quickActions": [
|
||||
{
|
||||
"label": "Doctor availability",
|
||||
"prompt": "What doctors are available and what are their visiting hours?"
|
||||
},
|
||||
{
|
||||
"label": "Clinic timings",
|
||||
"prompt": "What are the clinic locations and timings?"
|
||||
},
|
||||
{
|
||||
"label": "Patient history",
|
||||
"prompt": "Can you summarize this patient's history?"
|
||||
},
|
||||
{
|
||||
"label": "Treatment packages",
|
||||
"prompt": "What treatment packages are available?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
62
data/theme-backups/theme-2026-04-02T09-53-00-903Z.json
Normal file
62
data/theme-backups/theme-2026-04-02T09-53-00-903Z.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"brand": {
|
||||
"name": "Helix Engage",
|
||||
"hospitalName": "Global Hospital",
|
||||
"logo": "/helix-logo.png",
|
||||
"favicon": "/favicon.ico"
|
||||
},
|
||||
"colors": {
|
||||
"brand": {
|
||||
"25": "rgb(248 250 252)",
|
||||
"50": "rgb(241 245 249)",
|
||||
"100": "rgb(226 232 240)",
|
||||
"200": "rgb(203 213 225)",
|
||||
"300": "rgb(148 163 184)",
|
||||
"400": "rgb(100 116 139)",
|
||||
"500": "rgb(71 85 105)",
|
||||
"600": "rgb(47 64 89)",
|
||||
"700": "rgb(37 49 72)",
|
||||
"800": "rgb(30 41 59)",
|
||||
"900": "rgb(15 23 42)",
|
||||
"950": "rgb(2 6 23)"
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"body": "'Plus Jakarta Sans', 'Inter', sans-serif",
|
||||
"display": "'Plus Jakarta Sans', 'Inter', sans-serif"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in to Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital",
|
||||
"showGoogleSignIn": false,
|
||||
"showForgotPassword": true,
|
||||
"poweredBy": {
|
||||
"label": "Powered by F0rty2.ai",
|
||||
"url": "https://f0rty2.ai"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital · {role}"
|
||||
},
|
||||
"ai": {
|
||||
"quickActions": [
|
||||
{
|
||||
"label": "Doctor availability",
|
||||
"prompt": "What doctors are available and what are their visiting hours?"
|
||||
},
|
||||
{
|
||||
"label": "Clinic timings",
|
||||
"prompt": "What are the clinic locations and timings?"
|
||||
},
|
||||
{
|
||||
"label": "Patient history",
|
||||
"prompt": "Can you summarize this patient's history?"
|
||||
},
|
||||
{
|
||||
"label": "Treatment packages",
|
||||
"prompt": "What treatment packages are available?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
62
data/theme-backups/theme-2026-04-02T10-00-48-735Z.json
Normal file
62
data/theme-backups/theme-2026-04-02T10-00-48-735Z.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"brand": {
|
||||
"name": "Helix Engage",
|
||||
"hospitalName": "Global Hospital",
|
||||
"logo": "/helix-logo.png",
|
||||
"favicon": "/favicon.ico"
|
||||
},
|
||||
"colors": {
|
||||
"brand": {
|
||||
"25": "rgb(240 253 250)",
|
||||
"50": "rgb(204 251 241)",
|
||||
"100": "rgb(153 246 228)",
|
||||
"200": "rgb(94 234 212)",
|
||||
"300": "rgb(45 212 191)",
|
||||
"400": "rgb(20 184 166)",
|
||||
"500": "rgb(13 148 136)",
|
||||
"600": "rgb(15 118 110)",
|
||||
"700": "rgb(17 94 89)",
|
||||
"800": "rgb(19 78 74)",
|
||||
"900": "rgb(17 63 61)",
|
||||
"950": "rgb(4 47 46)"
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"body": "'Plus Jakarta Sans', 'Inter', sans-serif",
|
||||
"display": "'Plus Jakarta Sans', 'Inter', sans-serif"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in to Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital",
|
||||
"showGoogleSignIn": false,
|
||||
"showForgotPassword": true,
|
||||
"poweredBy": {
|
||||
"label": "Powered by F0rty2.ai",
|
||||
"url": "https://f0rty2.ai"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital · {role}"
|
||||
},
|
||||
"ai": {
|
||||
"quickActions": [
|
||||
{
|
||||
"label": "Doctor availability",
|
||||
"prompt": "What doctors are available and what are their visiting hours?"
|
||||
},
|
||||
{
|
||||
"label": "Clinic timings",
|
||||
"prompt": "What are the clinic locations and timings?"
|
||||
},
|
||||
{
|
||||
"label": "Patient history",
|
||||
"prompt": "Can you summarize this patient's history?"
|
||||
},
|
||||
{
|
||||
"label": "Treatment packages",
|
||||
"prompt": "What treatment packages are available?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
62
data/theme-backups/theme-2026-04-02T10-19-29-559Z.json
Normal file
62
data/theme-backups/theme-2026-04-02T10-19-29-559Z.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"brand": {
|
||||
"name": "Helix Engage",
|
||||
"hospitalName": "Global Hospital",
|
||||
"logo": "/helix-logo.png",
|
||||
"favicon": "/favicon.ico"
|
||||
},
|
||||
"colors": {
|
||||
"brand": {
|
||||
"25": "rgb(240 253 250)",
|
||||
"50": "rgb(204 251 241)",
|
||||
"100": "rgb(153 246 228)",
|
||||
"200": "rgb(94 234 212)",
|
||||
"300": "rgb(45 212 191)",
|
||||
"400": "rgb(20 184 166)",
|
||||
"500": "rgb(13 148 136)",
|
||||
"600": "rgb(15 118 110)",
|
||||
"700": "rgb(17 94 89)",
|
||||
"800": "rgb(19 78 74)",
|
||||
"900": "rgb(17 63 61)",
|
||||
"950": "rgb(4 47 46)"
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"body": "'Satoshi', 'Inter', -apple-system, sans-serif",
|
||||
"display": "'Satoshi', 'Inter', -apple-system, sans-serif"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in to Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital",
|
||||
"showGoogleSignIn": false,
|
||||
"showForgotPassword": true,
|
||||
"poweredBy": {
|
||||
"label": "Powered by F0rty2.ai",
|
||||
"url": "https://f0rty2.ai"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital · {role}"
|
||||
},
|
||||
"ai": {
|
||||
"quickActions": [
|
||||
{
|
||||
"label": "Doctor availability",
|
||||
"prompt": "What doctors are available and what are their visiting hours?"
|
||||
},
|
||||
{
|
||||
"label": "Clinic timings",
|
||||
"prompt": "What are the clinic locations and timings?"
|
||||
},
|
||||
{
|
||||
"label": "Patient history",
|
||||
"prompt": "Can you summarize this patient's history?"
|
||||
},
|
||||
{
|
||||
"label": "Treatment packages",
|
||||
"prompt": "What treatment packages are available?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
64
data/theme-backups/theme-2026-04-02T10-19-35-284Z.json
Normal file
64
data/theme-backups/theme-2026-04-02T10-19-35-284Z.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"brand": {
|
||||
"name": "Helix Engage",
|
||||
"hospitalName": "Global Hospital",
|
||||
"logo": "/helix-logo.png",
|
||||
"favicon": "/favicon.ico"
|
||||
},
|
||||
"colors": {
|
||||
"brand": {
|
||||
"25": "rgb(249 252 243)",
|
||||
"50": "rgb(244 249 231)",
|
||||
"100": "rgb(235 244 210)",
|
||||
"200": "rgb(224 247 161)",
|
||||
"300": "rgb(206 243 104)",
|
||||
"400": "rgb(195 255 31)",
|
||||
"500": "rgb(172 235 0)",
|
||||
"600": "rgb(142 194 0)",
|
||||
"700": "rgb(116 158 0)",
|
||||
"800": "rgb(97 133 0)",
|
||||
"900": "rgb(75 102 0)",
|
||||
"950": "rgb(49 66 0)"
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"body": "'Satoshi', 'Inter', -apple-system, sans-serif",
|
||||
"display": "'Satoshi', 'Inter', -apple-system, sans-serif"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in to Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital",
|
||||
"showGoogleSignIn": false,
|
||||
"showForgotPassword": true,
|
||||
"poweredBy": {
|
||||
"label": "Powered by F0rty2.ai",
|
||||
"url": "https://f0rty2.ai"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital · {role}"
|
||||
},
|
||||
"ai": {
|
||||
"quickActions": [
|
||||
{
|
||||
"label": "Doctor availability",
|
||||
"prompt": "What doctors are available and what are their visiting hours?"
|
||||
},
|
||||
{
|
||||
"label": "Clinic timings",
|
||||
"prompt": "What are the clinic locations and timings?"
|
||||
},
|
||||
{
|
||||
"label": "Patient history",
|
||||
"prompt": "Can you summarize this patient's history?"
|
||||
},
|
||||
{
|
||||
"label": "Treatment packages",
|
||||
"prompt": "What treatment packages are available?"
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1,
|
||||
"updatedAt": "2026-04-02T10:19:29.559Z"
|
||||
}
|
||||
64
data/theme.json
Normal file
64
data/theme.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"brand": {
|
||||
"name": "Helix Engage",
|
||||
"hospitalName": "Global Hospital",
|
||||
"logo": "/helix-logo.png",
|
||||
"favicon": "/favicon.ico"
|
||||
},
|
||||
"colors": {
|
||||
"brand": {
|
||||
"25": "rgb(250 245 255)",
|
||||
"50": "rgb(245 235 255)",
|
||||
"100": "rgb(235 215 254)",
|
||||
"200": "rgb(214 187 251)",
|
||||
"300": "rgb(182 146 246)",
|
||||
"400": "rgb(158 119 237)",
|
||||
"500": "rgb(127 86 217)",
|
||||
"600": "rgb(105 65 198)",
|
||||
"700": "rgb(83 56 158)",
|
||||
"800": "rgb(66 48 125)",
|
||||
"900": "rgb(53 40 100)",
|
||||
"950": "rgb(44 28 95)"
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"body": "'Satoshi', 'Inter', -apple-system, sans-serif",
|
||||
"display": "'Satoshi', 'Inter', -apple-system, sans-serif"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in to Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital",
|
||||
"showGoogleSignIn": false,
|
||||
"showForgotPassword": true,
|
||||
"poweredBy": {
|
||||
"label": "Powered by F0rty2.ai",
|
||||
"url": "https://f0rty2.ai"
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Ramaiah",
|
||||
"subtitle": "Ramaiah Hospital · {role}"
|
||||
},
|
||||
"ai": {
|
||||
"quickActions": [
|
||||
{
|
||||
"label": "Doctor availability",
|
||||
"prompt": "What doctors are available and what are their visiting hours?"
|
||||
},
|
||||
{
|
||||
"label": "Clinic timings",
|
||||
"prompt": "What are the clinic locations and timings?"
|
||||
},
|
||||
{
|
||||
"label": "Patient history",
|
||||
"prompt": "Can you summarize this patient's history?"
|
||||
},
|
||||
{
|
||||
"label": "Treatment packages",
|
||||
"prompt": "What treatment packages are available?"
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 2,
|
||||
"updatedAt": "2026-04-02T10:19:35.284Z"
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { RecordingsModule } from './recordings/recordings.module';
|
||||
import { EventsModule } from './events/events.module';
|
||||
import { CallerResolutionModule } from './caller/caller-resolution.module';
|
||||
import { RulesEngineModule } from './rules-engine/rules-engine.module';
|
||||
import { ConfigThemeModule } from './config/config-theme.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -42,6 +43,7 @@ import { RulesEngineModule } from './rules-engine/rules-engine.module';
|
||||
EventsModule,
|
||||
CallerResolutionModule,
|
||||
RulesEngineModule,
|
||||
ConfigThemeModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
10
src/config/config-theme.module.ts
Normal file
10
src/config/config-theme.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ThemeController } from './theme.controller';
|
||||
import { ThemeService } from './theme.service';
|
||||
|
||||
@Module({
|
||||
controllers: [ThemeController],
|
||||
providers: [ThemeService],
|
||||
exports: [ThemeService],
|
||||
})
|
||||
export class ConfigThemeModule {}
|
||||
27
src/config/theme.controller.ts
Normal file
27
src/config/theme.controller.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Controller, Get, Put, Post, Body, Logger } from '@nestjs/common';
|
||||
import { ThemeService } from './theme.service';
|
||||
import type { ThemeConfig } from './theme.defaults';
|
||||
|
||||
@Controller('api/config')
|
||||
export class ThemeController {
|
||||
private readonly logger = new Logger(ThemeController.name);
|
||||
|
||||
constructor(private readonly theme: ThemeService) {}
|
||||
|
||||
@Get('theme')
|
||||
getTheme() {
|
||||
return this.theme.getTheme();
|
||||
}
|
||||
|
||||
@Put('theme')
|
||||
updateTheme(@Body() body: Partial<ThemeConfig>) {
|
||||
this.logger.log('Theme update request');
|
||||
return this.theme.updateTheme(body);
|
||||
}
|
||||
|
||||
@Post('theme/reset')
|
||||
resetTheme() {
|
||||
this.logger.log('Theme reset request');
|
||||
return this.theme.resetTheme();
|
||||
}
|
||||
}
|
||||
79
src/config/theme.defaults.ts
Normal file
79
src/config/theme.defaults.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
export type ThemeConfig = {
|
||||
version?: number;
|
||||
updatedAt?: string;
|
||||
brand: {
|
||||
name: string;
|
||||
hospitalName: string;
|
||||
logo: string;
|
||||
favicon: string;
|
||||
};
|
||||
colors: {
|
||||
brand: Record<string, string>;
|
||||
};
|
||||
typography: {
|
||||
body: string;
|
||||
display: string;
|
||||
};
|
||||
login: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
showGoogleSignIn: boolean;
|
||||
showForgotPassword: boolean;
|
||||
poweredBy: { label: string; url: string };
|
||||
};
|
||||
sidebar: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
};
|
||||
ai: {
|
||||
quickActions: Array<{ label: string; prompt: string }>;
|
||||
};
|
||||
};
|
||||
|
||||
export const DEFAULT_THEME: ThemeConfig = {
|
||||
brand: {
|
||||
name: 'Helix Engage',
|
||||
hospitalName: 'Global Hospital',
|
||||
logo: '/helix-logo.png',
|
||||
favicon: '/favicon.ico',
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
'25': 'rgb(239 246 255)',
|
||||
'50': 'rgb(219 234 254)',
|
||||
'100': 'rgb(191 219 254)',
|
||||
'200': 'rgb(147 197 253)',
|
||||
'300': 'rgb(96 165 250)',
|
||||
'400': 'rgb(59 130 246)',
|
||||
'500': 'rgb(37 99 235)',
|
||||
'600': 'rgb(29 78 216)',
|
||||
'700': 'rgb(30 64 175)',
|
||||
'800': 'rgb(30 58 138)',
|
||||
'900': 'rgb(23 37 84)',
|
||||
'950': 'rgb(15 23 42)',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
body: "'Satoshi', var(--font-inter, 'Inter'), -apple-system, 'Segoe UI', Roboto, Arial, sans-serif",
|
||||
display: "'General Sans', var(--font-inter, 'Inter'), -apple-system, 'Segoe UI', Roboto, Arial, sans-serif",
|
||||
},
|
||||
login: {
|
||||
title: 'Sign in to Helix Engage',
|
||||
subtitle: 'Global Hospital',
|
||||
showGoogleSignIn: true,
|
||||
showForgotPassword: true,
|
||||
poweredBy: { label: 'Powered by F0rty2.ai', url: 'https://f0rty2.ai' },
|
||||
},
|
||||
sidebar: {
|
||||
title: 'Helix Engage',
|
||||
subtitle: 'Global Hospital \u00b7 {role}',
|
||||
},
|
||||
ai: {
|
||||
quickActions: [
|
||||
{ label: 'Doctor availability', prompt: 'What doctors are available and what are their visiting hours?' },
|
||||
{ label: 'Clinic timings', prompt: 'What are the clinic locations and timings?' },
|
||||
{ label: 'Patient history', prompt: "Can you summarize this patient's history?" },
|
||||
{ label: 'Treatment packages', prompt: 'What treatment packages are available?' },
|
||||
],
|
||||
},
|
||||
};
|
||||
98
src/config/theme.service.ts
Normal file
98
src/config/theme.service.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { DEFAULT_THEME, type ThemeConfig } from './theme.defaults';
|
||||
|
||||
const THEME_PATH = join(process.cwd(), 'data', 'theme.json');
|
||||
const BACKUP_DIR = join(process.cwd(), 'data', 'theme-backups');
|
||||
|
||||
@Injectable()
|
||||
export class ThemeService implements OnModuleInit {
|
||||
private readonly logger = new Logger(ThemeService.name);
|
||||
private cached: ThemeConfig | null = null;
|
||||
|
||||
onModuleInit() {
|
||||
this.load();
|
||||
}
|
||||
|
||||
getTheme(): ThemeConfig {
|
||||
if (this.cached) return this.cached;
|
||||
return this.load();
|
||||
}
|
||||
|
||||
updateTheme(updates: Partial<ThemeConfig>): ThemeConfig {
|
||||
const current = this.getTheme();
|
||||
|
||||
const merged: ThemeConfig = {
|
||||
brand: { ...current.brand, ...updates.brand },
|
||||
colors: {
|
||||
brand: { ...current.colors.brand, ...updates.colors?.brand },
|
||||
},
|
||||
typography: { ...current.typography, ...updates.typography },
|
||||
login: { ...current.login, ...updates.login, poweredBy: { ...current.login.poweredBy, ...updates.login?.poweredBy } },
|
||||
sidebar: { ...current.sidebar, ...updates.sidebar },
|
||||
ai: {
|
||||
quickActions: updates.ai?.quickActions ?? current.ai.quickActions,
|
||||
},
|
||||
};
|
||||
|
||||
merged.version = (current.version ?? 0) + 1;
|
||||
merged.updatedAt = new Date().toISOString();
|
||||
|
||||
this.backup();
|
||||
|
||||
const dir = dirname(THEME_PATH);
|
||||
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(THEME_PATH, JSON.stringify(merged, null, 2), 'utf8');
|
||||
this.cached = merged;
|
||||
|
||||
this.logger.log(`Theme updated to v${merged.version}`);
|
||||
return merged;
|
||||
}
|
||||
|
||||
resetTheme(): ThemeConfig {
|
||||
this.backup();
|
||||
const dir = dirname(THEME_PATH);
|
||||
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(THEME_PATH, JSON.stringify(DEFAULT_THEME, null, 2), 'utf8');
|
||||
this.cached = DEFAULT_THEME;
|
||||
this.logger.log('Theme reset to defaults');
|
||||
return DEFAULT_THEME;
|
||||
}
|
||||
|
||||
private load(): ThemeConfig {
|
||||
try {
|
||||
if (existsSync(THEME_PATH)) {
|
||||
const raw = readFileSync(THEME_PATH, 'utf8');
|
||||
const parsed = JSON.parse(raw);
|
||||
this.cached = {
|
||||
brand: { ...DEFAULT_THEME.brand, ...parsed.brand },
|
||||
colors: { brand: { ...DEFAULT_THEME.colors.brand, ...parsed.colors?.brand } },
|
||||
typography: { ...DEFAULT_THEME.typography, ...parsed.typography },
|
||||
login: { ...DEFAULT_THEME.login, ...parsed.login, poweredBy: { ...DEFAULT_THEME.login.poweredBy, ...parsed.login?.poweredBy } },
|
||||
sidebar: { ...DEFAULT_THEME.sidebar, ...parsed.sidebar },
|
||||
ai: { quickActions: parsed.ai?.quickActions ?? DEFAULT_THEME.ai.quickActions },
|
||||
};
|
||||
this.logger.log('Theme loaded from file');
|
||||
return this.cached;
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(`Failed to load theme: ${err}`);
|
||||
}
|
||||
|
||||
this.cached = DEFAULT_THEME;
|
||||
this.logger.log('Using default theme');
|
||||
return DEFAULT_THEME;
|
||||
}
|
||||
|
||||
private backup() {
|
||||
try {
|
||||
if (!existsSync(THEME_PATH)) return;
|
||||
if (!existsSync(BACKUP_DIR)) mkdirSync(BACKUP_DIR, { recursive: true });
|
||||
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
copyFileSync(THEME_PATH, join(BACKUP_DIR, `theme-${ts}.json`));
|
||||
} catch (err) {
|
||||
this.logger.warn(`Backup failed: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user