From dac7d7210d8e5db826ff5cebbab21f76fcec88ff Mon Sep 17 00:00:00 2001 From: Manfred Steyer Date: Sun, 31 May 2026 11:09:51 +0200 Subject: [PATCH 1/2] feat: prepare starter kit --- .cursor/rules/cursor.mdc | 69 - .nx/cache/run.json | 21 - .../terminalOutputs/10056352697178169218 | 7 - .../terminalOutputs/12042095199391608829 | 7 - .../terminalOutputs/12831485786904232757 | 7 - .../terminalOutputs/16420853343477340724 | 7 - .nx/cache/terminalOutputs/3182805582318340814 | 17 - .nx/cache/terminalOutputs/3441099009790142996 | 7 - .nx/cache/terminalOutputs/5705350011921048267 | 7 - .nx/cache/terminalOutputs/9008508769978719145 | 7 - .nx/cache/terminalOutputs/946553374700050056 | 17 - .../3C4963C9-A4DD-5FC7-B3EF-01EB563EE45E.db | Bin 53248 -> 0 bytes .nx/workspace-data/d/daemon.log | 1145 - .nx/workspace-data/file-map.json | 949 - .nx/workspace-data/lockfile-dependencies.hash | 1 - .nx/workspace-data/lockfile-nodes.hash | 1 - .nx/workspace-data/nx_files.nxt | Bin 23300 -> 0 bytes .../parsed-lock-file.dependencies.json | 10957 -------- .../parsed-lock-file.nodes.json | 11423 -------- .nx/workspace-data/project-graph.json | 21601 ---------------- .nx/workspace-data/project-graph.lock | 0 .nx/workspace-data/source-maps.json | 275 - .../advanced-data-table.css | 23 - .../advanced-data-table.html | 45 - .../advanced-data-table.ts | 132 - .../shared/ui-common/click-with-warning.ts | 35 - .../ui-common/custom-template-outlet.ts | 36 - .../shared/ui-common/data-table/data-table.ts | 39 - .../ui-common/data-table/table-field.ts | 13 - .../shared/ui-common/dialog/dialog-event.ts | 6 - .../shared/ui-common/dialog/dialog-info.ts | 6 - .../ui-common/dialog/dialog-outlet-service.ts | 39 - .../shared/ui-common/dialog/dialog-outlet.css | 29 - .../ui-common/dialog/dialog-outlet.html | 12 - .../shared/ui-common/dialog/dialog-outlet.ts | 57 - .../shared/ui-common/dialog/dialog.service.ts | 30 - .../shared/ui-common/dialog/dialog.token.ts | 3 - .../ui-common/injection-tabbed-pane/tab.ts | 40 - .../injection-tabbed-pane/tabbed-pane.ts | 86 - .../shared/ui-common/query-tabbed-pane/tab.ts | 42 - .../query-tabbed-pane/tabbed-pane.ts | 113 - .../service-tabbed-pane/tab-registry.ts | 25 - .../ui-common/service-tabbed-pane/tab.ts | 41 - .../service-tabbed-pane/tabbed-pane.ts | 82 - .../shared/ui-common/simple-tooltip.ts | 75 - src/app/domains/shared/ui-common/tooltip.ts | 88 - src/app/shell/about/about.html | 101 +- src/app/shell/about/about.ts | 37 +- src/app/shell/about/demo-dialog.ts | 27 - 49 files changed, 2 insertions(+), 47785 deletions(-) delete mode 100644 .cursor/rules/cursor.mdc delete mode 100644 .nx/cache/run.json delete mode 100644 .nx/cache/terminalOutputs/10056352697178169218 delete mode 100644 .nx/cache/terminalOutputs/12042095199391608829 delete mode 100644 .nx/cache/terminalOutputs/12831485786904232757 delete mode 100644 .nx/cache/terminalOutputs/16420853343477340724 delete mode 100644 .nx/cache/terminalOutputs/3182805582318340814 delete mode 100644 .nx/cache/terminalOutputs/3441099009790142996 delete mode 100644 .nx/cache/terminalOutputs/5705350011921048267 delete mode 100644 .nx/cache/terminalOutputs/9008508769978719145 delete mode 100644 .nx/cache/terminalOutputs/946553374700050056 delete mode 100644 .nx/workspace-data/3C4963C9-A4DD-5FC7-B3EF-01EB563EE45E.db delete mode 100644 .nx/workspace-data/d/daemon.log delete mode 100644 .nx/workspace-data/file-map.json delete mode 100644 .nx/workspace-data/lockfile-dependencies.hash delete mode 100644 .nx/workspace-data/lockfile-nodes.hash delete mode 100644 .nx/workspace-data/nx_files.nxt delete mode 100644 .nx/workspace-data/parsed-lock-file.dependencies.json delete mode 100644 .nx/workspace-data/parsed-lock-file.nodes.json delete mode 100644 .nx/workspace-data/project-graph.json delete mode 100644 .nx/workspace-data/project-graph.lock delete mode 100644 .nx/workspace-data/source-maps.json delete mode 100644 src/app/domains/shared/ui-common/advanced-data-table/advanced-data-table.css delete mode 100644 src/app/domains/shared/ui-common/advanced-data-table/advanced-data-table.html delete mode 100644 src/app/domains/shared/ui-common/advanced-data-table/advanced-data-table.ts delete mode 100644 src/app/domains/shared/ui-common/click-with-warning.ts delete mode 100644 src/app/domains/shared/ui-common/custom-template-outlet.ts delete mode 100644 src/app/domains/shared/ui-common/data-table/data-table.ts delete mode 100644 src/app/domains/shared/ui-common/data-table/table-field.ts delete mode 100644 src/app/domains/shared/ui-common/dialog/dialog-event.ts delete mode 100644 src/app/domains/shared/ui-common/dialog/dialog-info.ts delete mode 100644 src/app/domains/shared/ui-common/dialog/dialog-outlet-service.ts delete mode 100644 src/app/domains/shared/ui-common/dialog/dialog-outlet.css delete mode 100644 src/app/domains/shared/ui-common/dialog/dialog-outlet.html delete mode 100644 src/app/domains/shared/ui-common/dialog/dialog-outlet.ts delete mode 100644 src/app/domains/shared/ui-common/dialog/dialog.service.ts delete mode 100644 src/app/domains/shared/ui-common/dialog/dialog.token.ts delete mode 100644 src/app/domains/shared/ui-common/injection-tabbed-pane/tab.ts delete mode 100644 src/app/domains/shared/ui-common/injection-tabbed-pane/tabbed-pane.ts delete mode 100644 src/app/domains/shared/ui-common/query-tabbed-pane/tab.ts delete mode 100644 src/app/domains/shared/ui-common/query-tabbed-pane/tabbed-pane.ts delete mode 100644 src/app/domains/shared/ui-common/service-tabbed-pane/tab-registry.ts delete mode 100644 src/app/domains/shared/ui-common/service-tabbed-pane/tab.ts delete mode 100644 src/app/domains/shared/ui-common/service-tabbed-pane/tabbed-pane.ts delete mode 100644 src/app/domains/shared/ui-common/simple-tooltip.ts delete mode 100644 src/app/domains/shared/ui-common/tooltip.ts delete mode 100644 src/app/shell/about/demo-dialog.ts diff --git a/.cursor/rules/cursor.mdc b/.cursor/rules/cursor.mdc deleted file mode 100644 index 0de9ad4e..00000000 --- a/.cursor/rules/cursor.mdc +++ /dev/null @@ -1,69 +0,0 @@ ---- -context: true -priority: high -scope: project ---- - -You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices. - -## TypeScript Best Practices - -- Use strict type checking -- Prefer type inference when the type is obvious -- Avoid the `any` type; use `unknown` when type is uncertain - -## Angular Best Practices - -- Always use standalone components over NgModules -- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+. -- Use signals for state management -- Implement lazy loading for feature routes -- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead -- Use `NgOptimizedImage` for all static images. - - `NgOptimizedImage` does not work for inline base64 images. - -## Accessibility Requirements - -- It MUST pass all AXE checks. -- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes. - -### Components - -- Keep components small and focused on a single responsibility -- Use `input()` and `output()` functions instead of decorators -- Use `computed()` for derived state -- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator -- Prefer inline templates for small components -- Prefer Reactive forms instead of Template-driven ones -- Do NOT use `ngClass`, use `class` bindings instead -- Do NOT use `ngStyle`, use `style` bindings instead -- When using external templates/styles, use paths relative to the component TS file. - -## State Management - -- Use signals for local component state -- Use `computed()` for derived state -- Keep state transformations pure and predictable -- Do NOT use `mutate` on signals, use `update` or `set` instead - -## NgRx Signal Store -- Use NgRx Signal Store for State Management -- If possible, put the store next to the consuming feature or component -- Use `withProps` for injecting services -- All Features (`withComputed`, `withMethods`, `withResource`, `withMutations`, etc.) get an expression-bodied arrow function (array function with *implicit* return) passed (e.g. `() => ({...})` and *not* `() => { return {...} }`) -- For NgRx Signal Store use the Resource and the Mutation API as described here: https://www.angulararchitects.io/blog/full-cycle-reativity-in-angular-signal-forms-signal-store-resources-mutation-api/ - -## Templates - -- Keep templates simple and avoid complex logic -- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch` -- Use the async pipe to handle observables -- Do not assume globals like (`new Date()`) are available. -- Do not write arrow functions in templates (they are not supported). - -## Services - -- Design services around a single responsibility -- Use the `providedIn: 'root'` option for singleton services -- Use the `inject()` function instead of constructor injection - diff --git a/.nx/cache/run.json b/.nx/cache/run.json deleted file mode 100644 index 04be8642..00000000 --- a/.nx/cache/run.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "run": { - "command": "nx lint", - "startTime": "2026-02-14T17:47:16.762Z", - "endTime": "2026-02-14T17:47:16.804Z", - "inner": false - }, - "tasks": [ - { - "taskId": "flights42:lint", - "target": "lint", - "projectName": "flights42", - "hash": "5705350011921048267", - "startTime": "2026-02-14T17:47:16.802Z", - "endTime": "2026-02-14T17:47:16.803Z", - "params": "", - "cacheStatus": "local-cache-hit", - "status": 0 - } - ] -} diff --git a/.nx/cache/terminalOutputs/10056352697178169218 b/.nx/cache/terminalOutputs/10056352697178169218 deleted file mode 100644 index 14ba9ec2..00000000 --- a/.nx/cache/terminalOutputs/10056352697178169218 +++ /dev/null @@ -1,7 +0,0 @@ -> nx run shared-util-auth:lint - - -Linting "shared-util-auth"... - -✔ All files pass linting - diff --git a/.nx/cache/terminalOutputs/12042095199391608829 b/.nx/cache/terminalOutputs/12042095199391608829 deleted file mode 100644 index f1308035..00000000 --- a/.nx/cache/terminalOutputs/12042095199391608829 +++ /dev/null @@ -1,7 +0,0 @@ -> nx run miles-feature-next-level:lint - - -Linting "miles-feature-next-level"... - -✔ All files pass linting - diff --git a/.nx/cache/terminalOutputs/12831485786904232757 b/.nx/cache/terminalOutputs/12831485786904232757 deleted file mode 100644 index 2aec1f55..00000000 --- a/.nx/cache/terminalOutputs/12831485786904232757 +++ /dev/null @@ -1,7 +0,0 @@ -> nx run miles-data:lint - - -Linting "miles-data"... - -✔ All files pass linting - diff --git a/.nx/cache/terminalOutputs/16420853343477340724 b/.nx/cache/terminalOutputs/16420853343477340724 deleted file mode 100644 index 3d277fde..00000000 --- a/.nx/cache/terminalOutputs/16420853343477340724 +++ /dev/null @@ -1,7 +0,0 @@ -> nx run flights42:lint - - -Linting "flights42"... - -All files pass linting. - diff --git a/.nx/cache/terminalOutputs/3182805582318340814 b/.nx/cache/terminalOutputs/3182805582318340814 deleted file mode 100644 index d1baa714..00000000 --- a/.nx/cache/terminalOutputs/3182805582318340814 +++ /dev/null @@ -1,17 +0,0 @@ - -Linting "miles-ui-common"... - -/Users/manfredsteyer/projects/trainings/flights42/flights42/libs/miles/ui-common/package.json - 4:3 error The "miles-ui-common" project uses the following packages, but they are missing from "peerDependencies": - - @flights42/miles-feature-manage @nx/dependency-checks - -/Users/manfredsteyer/projects/trainings/flights42/flights42/libs/miles/ui-common/src/lib/miles-ui-common/miles-ui-common.ts - 4:1 error A project without tags matching at least one constraint cannot depend on any libraries @nx/enforce-module-boundaries - -✖ 2 problems (2 errors, 0 warnings) - 1 error and 0 warnings potentially fixable with the `--fix` option. - -✖ 2 problems (2 errors, 0 warnings) - - 1 error and 0 warnings are potentially fixable with the `--fix` option. - diff --git a/.nx/cache/terminalOutputs/3441099009790142996 b/.nx/cache/terminalOutputs/3441099009790142996 deleted file mode 100644 index acae3522..00000000 --- a/.nx/cache/terminalOutputs/3441099009790142996 +++ /dev/null @@ -1,7 +0,0 @@ -> nx run miles-feature-manage:lint - - -Linting "miles-feature-manage"... - -✔ All files pass linting - diff --git a/.nx/cache/terminalOutputs/5705350011921048267 b/.nx/cache/terminalOutputs/5705350011921048267 deleted file mode 100644 index 3d277fde..00000000 --- a/.nx/cache/terminalOutputs/5705350011921048267 +++ /dev/null @@ -1,7 +0,0 @@ -> nx run flights42:lint - - -Linting "flights42"... - -All files pass linting. - diff --git a/.nx/cache/terminalOutputs/9008508769978719145 b/.nx/cache/terminalOutputs/9008508769978719145 deleted file mode 100644 index 0c926a7b..00000000 --- a/.nx/cache/terminalOutputs/9008508769978719145 +++ /dev/null @@ -1,7 +0,0 @@ -> nx run miles:lint - - -Linting "miles"... - -✔ All files pass linting - diff --git a/.nx/cache/terminalOutputs/946553374700050056 b/.nx/cache/terminalOutputs/946553374700050056 deleted file mode 100644 index b41c734f..00000000 --- a/.nx/cache/terminalOutputs/946553374700050056 +++ /dev/null @@ -1,17 +0,0 @@ - -Linting "miles-ui-common"... - -/Users/manfredsteyer/projects/trainings/flights42/flights42/libs/miles/ui-common/package.json - 4:3 error The "miles-ui-common" project uses the following packages, but they are missing from "peerDependencies": - - @flights42/miles-feature-manage @nx/dependency-checks - -/Users/manfredsteyer/projects/trainings/flights42/flights42/libs/miles/ui-common/src/lib/miles-ui-common/miles-ui-common.ts - 4:1 error A project tagged with "type:ui" can only depend on libs tagged with "type:data", "type:util" @nx/enforce-module-boundaries - -✖ 2 problems (2 errors, 0 warnings) - 1 error and 0 warnings potentially fixable with the `--fix` option. - -✖ 2 problems (2 errors, 0 warnings) - - 1 error and 0 warnings are potentially fixable with the `--fix` option. - diff --git a/.nx/workspace-data/3C4963C9-A4DD-5FC7-B3EF-01EB563EE45E.db b/.nx/workspace-data/3C4963C9-A4DD-5FC7-B3EF-01EB563EE45E.db deleted file mode 100644 index bdc31cfd4fe8d93445e26a0544430095f351c43b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI5ZEPD!8OL|y*pA#Ns6kVG1Qm3>ZkmXCZjjxWK z*z3|4@gaS=uLxBqB<^$uBt9V_5C~c!fsPY!5*&9Y-~%d1yd65I1QLRBe5l0Cc-`*C zyW1C#py@MmoY|dc{`)+?ncc**GwWl|&zCHdKUJ-*7cE|9dYP^+W{T$-hUvmTDg4t; zQJm>+Z{XjoBkz~(V#Y6Qq|gUUGB(SgL+D4`$GF?6S&m8eCBB!~Obo|n+%4(!sJOCX z*6aU)!0h5uZtmFy8u)&KWQQ=ROK4CZL z+`==t6MX%y4Zpb1a?Q5m_qWQ%=3|lc@F;6iO%ly^#kfncZPBSmNgVBoqz@cmPttB@ z2Uj;Y8)gMVZ^hc)jJdkrw!;0D;<|Ydn~q(QqS5r|D0^ie)pW+{%=fs|oF#TYF0J0} zgwCEjy_{d1Tfmi%;BK?6dy$p)c9z%?(`LW_Rc@nJebHR8?5m+W zFx=LsP4A+WYUNbvbfZ?ZO4UlMa(qnhj-;O(^}8lGeJqI1t?&b}oef zmiCapr{n`Zi*7Oa1qlcM0U!VbfB+Bx0zd!=00AHX1b_e#c>D?Uv!Rha3nvQ4b4$nO z78fLGTppL>I4&0bjlnNSKmZ5;0U!VbfB+Bx0zd!=00AHX1c1PfA&?4<#O{p(ko*5} zZieA*abG}RLtAJO4RIFtCi*4b2?+=Q0U!VbfB+Bx0zd!=00AHX1c1Oj0*Cg5<-O?- z8#pPlAq$$Isj8$HqIjxYI=yD$#Xq&P=BjztELS(y&5HHWfv|XBfH4$oC(F8`3xc5H zm$qIioApeilp$*vs+Dr7Vtu4HtbS;K5j8~;ve;UY6G7>Xha26o2K4M9{S!?0s(*TO3_ilf00e*l5C8%|00;m9Ah3f8 zko*5b@oNlv7kv?JqNmX=?)Ti6xH>1M{+jw~s+>BI{B!a<$@57)aXazNL_Lv948(tq zD?kDQKmZ5;0U!VbfB+Bx0{4ePU3hsa z-ge<3+f8d;rV3Z4Vr_+oY&We*&MvLl$n=nQ!`;`T|KB^g)H{D9=In1LqmPK4y+H+T zWFq8v_vou5(j>*HY@AU*j#X5>5ld$VPq>9)i1IF}seAEdCkdgl-0p}X>()ASwo z|NJSne|NmAZST=yPWxNb{)ZXXcE_WaI?R9ZV`BcB3G(@$MSo%N3lb0j0zd!=00AHX z1b_e#00KY&2mk>fupN%sU1!icD9IJkOWaLPK;K5A z+=bNpsKQ-CP44g99QqSF&b^U(Cv_bi=DxxWaqN!30YLx(AOHk_01yBIKmZ5;0U!Vb z9s>e?&vWzHpZ0rJ-Js92?TC)Y{rTN~kE-h@6+G^Mx1LDnaqGIz?@@L1>3JSgI*^Y` zoKMIpKUb4Jt|n*&4_D)UuEu;^>hWi00AHX1b_e#00KY&2mk>f00f?B0@Klm+QZ7S z*K;wRmjp@62vSB=cu}8_l?g%h*fkZ2$V9?pa>4vIb7?}-w(Y{A@^sjC!ef1YZJWK1 z#8h|736BX2FLo+;gdtCd+D>$?;y?a?5?!{0$CQH?9!z42ZN=enV}E;dNE4#&7FF~K zQ6u;NqiC7T|9=V3`(H*)e3!rSM0aus9tZ#dAOHk_01yBIKmZ5;0U!VbfWU(hpp)=5 cMG~^AEGx32>#`!~l0xVIdklR159j~?8JL)Ha0f)+H0@#Xo{b`o%fcnb{~G}Zx5h-Ag>4U;^({v^XtIkZ8?T` z8c6LyhW~KdNANn>(Dv`Y1FbRdV?J{z^kKYy3;J=iwx3%4JRaWf@|rV$E4dpR9YbrZ zr}O@3UdQk{&hTwNwZ|LY3AFb!^FE*VS_4h-I*B*-GpF!g>k<88htJ#dPUY82c)g65 zK7R)7nY`4uOM72h{nujBOwUKj9E|9$DJ{(29q*aN#h@$}=;>LZ@oH}mT)ysTWre=D^8 zyITECbD%H9=fwL<`n-qNMW*k2X|)Fz)7tf3!ut>K`VcRDMn4}m?=Lm&M`-_nmtFtI zc+XEUkH-51gy#PwZ8^fm#-|L=()# zy}|NeIGP)e{IKIkp%+JSC+h@>A9b>DZ8$h9?~O<8mHu)*YHN=E>U@4qv5LS;{VdMt z7IwU(6DD!8*6nTYF6MJ*jRvcJ5_qv6CP^B3X%@v%C-Ns3IbtFCe0#m$TFB?~VRzgg zthUb{@|^i_t~X*$UeNKqFp45CjKjq9gKU%!cjUv?U@c$m_E~ovhC!Hxagz8+2SLPM zXVx_1{&K6kK3;0;*PI6G_i~0vGoFG8QZEc6--|nOHaSFZDerCXueRsA<1Ugbh*p1f zVIavN2QSXTEJ?D2L8367{CK?^#Q@#@us7^3jQMbohC$%_p&!L5PnsgK*&nuBxtgCd zZY?bL7nj%@muL-;&5nM4c6)U&T30zXUH;ULa}Aj`H`=X$?g zPSEOghx6`x79Dlc#PbkUlEiVAdRf}gzj|xCqfvh}?yip8EBR=IdbG~&&oAbB_{j5; zFbxt(Uu%lehKkgZ>-O8DouhHS(n2j)*7SKF^*~n{0lh_r0qQnIXRYmwN2qF?L}3FCuu`j>Itp1w+#kt{bGBKC&^bAQNDb>Kb|19nh4#CBD5*;J82vxJlE{KEEJ)f ztkqlY=c~m7`5iulR3qO|F@LNtO*%;!2VoG$QNw;3gBi=8 zI$VrT?cfV|3_q2V-AbW`gY_|w#4?x}Ka@sM;AMf|iL$s-tkF`wyxbo3=ksmdp>6q0 z5+oTyz-{5Tc#g!cK4P7?UAY{W5kzmWy3il4@cGnBL*EZPmfH!_1RpY~DfZ)i`{DV1 zcX_bbE}IsMU&ag-QQ|Q5lGO78jLB!Hw3E&C)`z3PusvLt>o1A9mH8g)%{)I$Lu3?t zzE9McoVPZi?pohO3~d#k<0nc2+_hK&>iIy?Th<$wM=*0Ee-S0V3~Lz{fxDo zAfk;z|QXt$=wUciqSZygpxr374pN?6-FvD|3t>LT0a;DJsD&5F@@ z!G;jo1c7??$F3}TXJJbqhEA3xUPNafZyGk%>gY)mIDiQ{U;-awO!UwJySs%Ww$EF+ z`QczZFwxa``bkbHh5$4Y`yKsYyfKl#5e0W=*9s-7V@st*SrVoAh=f@pq=n>?Nkgl} zU}V18zzsfOad;(c0>__59fs97HITz^bpnh9e-a^c-}4&MswXP#atF`?g9NU%^5GBw z*=}$OTb1geD;_Q}0IuRzh>mH)VbsH0FSky#T4@pN*Ot5E+`6j5iDW*A69h~Yi9I3U zNvSl2UPE6%M2UOx1mpgSU=iU5PsFb<@FEaR$FJOy0C(l%g9xlqxZEi70KZ8V&z`x6 z?rTHvWov{1t#svZfUkH?p8yX|Nab-R2e5*096nHQ*zJu=IcvF5!iI*0ulAxO3xUKY zKd@n=DFCb6-T58e)gB?#V7mgySTx$w-nKVU#@>ZSh*Y7pJpieN(c1d9<$kYSc`)I9 z4|5X^2S%fD#K6YmS*T)7&eGO}R_Jw-!E*=L7r`luNd*9L0S2e%R<^@meSkAKfGz4E z6cUDxS3!{4C5=aB!OQ(^Ws>59_<5!vNIf3}$pcOb({4%S*D4tn5eXQwoVTa&%KQZr zPL;)=r=af?G0rWmkGAhba&mpGmC+(zEX%UBc#^7u1mtd-)m}A;ZXC5)y2jiAR`?F@(usYLQm& zYL5t}U`z208L*M1V??t*kLMi^2Fs)N`Wm9Osxv2Eu|@1oj4-?eD-SW1aCgQcjcm!c zRUQr1E(b)Xaa0-T&JRIj{7ScS2&1*US9%2SG~ffH03*djtGEdZt!yb<6ezV3`({PM z0zFYDAoC1U6-Ck*=-ITqdV)ssELrh`gAMp4sVjHbA`)3&YePVdG>p|wGMIt12iS!4 zyjY<-Wosul)}li@yrnmxMb=7=O;v@wjEs*Yg*~aFs|{Dvo|Yhub1drxWFIcY`Y5ze zt-@#+m%Reo{H8Im2-Tx90y^)D_!k51Krhq_@VCBTR6`n-ag+rj6J0CcMM9VYyxAKAo@bOEWp7l2`~FE5x|?2r44)SU9PLtY3Hh^dMJsu0_> zX!gQmk2>bWV@W3k4oL`8a1|f%F#m9F2T^)HZ&$%e)kJhLqQpRg&GXWiuxFQCI?C1V z4yD&+qg5gt(g=iG4!#hD37er}u+%Jm; zpmG)lng!7cy~ODjyJ!u~E%(i;{W;~qsvLw&P~Z+Jcu|$b3;7f)x~q%p%iUq&`iKRj z=r|NQ0O82;LQ`yA91Iqhg_S#Gs7SRFhaNq_Tv0=ht>!pSU4kyqhb_1Y%aQJ)NrH=b zL+aN{`LMsRU~^&$2in9i%rG2$hzdm%IVkkal5)RQ`^xHFOP-^>nJjIZQU^~#?G*?_ zo`@rv6Br&0%f&{hqf!E;?;y97{AF_D+Q97A%yhA)5?9$^Rg%rTOwK%|0)Zh0X%<#$ zZIyKPG%kjB-b-$*Soj;QM|PpYX{Az@FsL#r@vUJGR=cLbv>LHV1bA9XR60MvyaJ$Knr#ID zlo9av`4R+BjX~7|JW@;~l%@w$J}iwW6QNeAuY_Y2LRG$ydjup_A}GC7cN;~w_I$oF zXqBnDR*Fpm{URA0zKyqAL7&;WHm#1p=+u@Jwp5df(KIZof|#>atWKD#cjWUFoi~$Z zdZ=O-uB6i;=|bsnh<=r5DT6LfgH)_UE}R;uysDC0!yVgf&_1H2CzHXpW!Q=~_!B5j z_1}vk>O~YWx4=VHbe->yC@$=@Im|po#y(Kg;8BuI^q9~_sH8LkPH$LdLl=Af@y@xm z{+c``)<8@EH~}4$jbi9CGziUWuj3mX|41#DI(jSw2GAkn#|ut4$kL@0>JtSW*|JX| zj^Y{#a$|$*l~%5B!YNLWj4SX*byvBmGH(>SVT?^TVLhPZj_kRCysOa{_8%M-5&ftq zkjyvXYrS9tKW3Ed?P-AIphTOU4*-K6x?>n}+US8k{{N=c3S!)O7-s_!(ys-WD4$ z<-oVdj=&%6A%2@7LNZbgmL~&mLpkPi*)d0hIyc6{WLZS1O0Nl)B>n?;u?M>tIku`Bi96*;-~x8aJRUe@cRbJnHCW?* zs=Sp=Ao296S>q@vNr4WkwKn~AwJS;$mIvMWDTy%9iP{Hf9Oy^kz3F%BO}3tQni(4m zV=TM$mKBcS9FfOQ;tr4)MT)mL%c>&xl$zPpI$D`DQ~M!y;8Zzd??l9#%#%T?L6yS} zS&vVx3z&<8vpa9aXYoq$;1;7);FfAF!dFbiN8&2Lh`XVkc7c_#xRG@lvv7C`?gM?z zRHa1nZJ#E#SPaQT><%HZdLE*4$ zr7vKzh8@%s7VSfr3*Q;t!-%Se>ktGb)}xkW#c^7Ryhz4Lxu|}@=LxW3KdaA=%4b*_tmJL|!k8(86~_Vid=<^W zAY15UrRi?%x;R3wbR3h>F%M`&?hUWrO#gnp&6c3aW+>cXj_5c7B1$)MkUbsTn2yW^ z5XguWzl#enex1?R_hV1{7ET1G{;eBCA`$QPqeDVa>_I+ZQFJh4bM zR3$iMYIaF((XA-G@d%u`>53I$i2wl%t5x4_jE$%+j?=S)9EkLki~+JS5iZ&agjnZU z0oRGr*#yb>ODr1MAaDv5s4&wqRQZ$askT1*U7pW0Jd5eIx z#qHk$MG)Fabqh`4HVZy|c9!k{ss5^Nlef4zvbtS6L8X9m58W*RJn`JSQ<=06Y}ytF zKK;c4o0;MgM`HYdJGfaXQFbbAuu`=9Yh`F@i=c`^s!M3|fzSXw3V|GX>->%iuhlX; z?(E%}elY?Sn0Ae#d=4Unu1j*x!VOE^Z}OYT;o#o4YNnm$=X8r} z)VLxpovTJiR0X8~p*fKP0b*DUkY1~Tn~a=0;4(f%x#Bh$H&{RgKxf|I7ztDj?_Q5$ zRn=<0F>%-2i~6d?JE|WNs0H>F<8i3>w#4e%SG7Q(12~5ipD4;jR^9k)#L|XBbeFkc zvWh)&Imj)ClMG-sROYE2Fb?6TVU~@tN_U|BVU{f2Sd8Z3Z%q~aUQ+dj z0iFwxN*FNr#n$)+PkCod@RO_)F(x2+zR}PuE;U5C3CkKR>6DptPuWjMW;pB6trRfM zG;AYCP*~h@&h{4ffmIkG`&T^#z!l-K2n>?N3K(F>t*6!)eb47cD$x}8Pr*^#bJLA$ z!08@rT9ul)x<8F6Z%qP`>WUmCDr^8B6mAjhW*FB*F{sEjH8w{~$V%t_I>OL_`W}q! z?swZI3a<|q16W{oU>S}xz}wZ(O(;{qU2-cVuN#r53W~Ek?$w&XG~v0YHMOq-lM(%N z;g(V;E`{Lto9>+1#p24T^Iif*Ply$|l>!*z^o1!Wt*ch>^5(|wSFZ~AqAW&k8_cM0 z5TWujJ(~kxI9SMm8kE>a+;!>d+8VV+k9=CmtB&coNY{{yJ%q7ZW@*#b=fEW7)Cd_5 zNJ@giZQ;ls{VaFaS9?q5Xj)(R;~O@(2ac$yMD9-dR7bbs_H%JdsnzFtHP29-z^O{Z z`Vn=;O|40B&kjUe=}=>DC3~I>XRV}zI+u|CR>z8DlheO_#Hy)57dJ^6g@cDAs#1lC z13Fctl=LdxQhBCcg+JgZ2vjoU8#yC=TM?J1?<8@hlQD6rBqT^GpG}{#KVx)woNd>p_(Fi3;5HojtJukedS&VWs#QobHdy^qp@kY`z|Z zm*8}qOonP1GL$(?`5IgOm}WLI>)v+0emGugo!uR-aupi=R~ZZq;wxJj%1H*6>;ck7 z>+bc-B64X|-E@x#!B9Wqdka~o(%1Tqx1?3w2GEz>ikq8r@)u&T$1yIBoD7wB0@*lbtp#s!r-)?pL} z_*h5WQB?~_U+C{{NYWa&Y2Bo@@XypQ2?@X@0u6_njTmDWRn_b_oqASG^W%@Us;wEog`brh&<4iw6fqU;oDd2yxDr3d~$I-j+&nHp)FtGaFHJqkiW z2uo9qp1ZciMd3Zz2N#%CD6R6}95r$yM;GXjp7Ma^7qgniVjrV6$O->FS*}SrC=9@v zI{XE30g5Ut*vy>WEYn=?NJ!=h=HLV_bF#}ECd}I6N^NG`vf*C`Eb02Z^0_37o z6YSQ**!bdJ2AMDf`DIWPnHHx4GTY)CDi(83<#V{+}uN`hO~y8UEg% z-q<+6@IClfz}GnY!N32he^SkRyWyws?RwPzzYPBjhyOJAkB8PcuW|TKfj+_TZ*chf zA14FD-}f{1`1+qKR~mli@MHMDfG;_89exY?3DBDNTv}HSCqk?Lr=9-#|17!T-{$b! z@GpVZILQ_DI4^*{-tgDpKh((WS?~{o*8iw^gVSH~e<`%){YP3Ww+wn{_&;;_`oA*o zHT(mwtoQ2)@V^UR>zRXZ$2k=GSMW9Oiygk?a4@v`pYQO05Bf;Me<$sIp7`=&e;)~d z9a`(W-06Qf^czh7`+v5c_eA*bGyKOmeEqMTpBerc@U8rhgTF7d*0a;$KNk7~Xw7>i zt$nUPfSxz}Upjor?SqDY(C6y$9|!*jhJQHxKYGF`#d945e{X2bdlIeHbM5be(3+l}}e{X1wpF8~LLO;;(-$85TqkTCE zTI;-?*4nA`>h*?ytHalGU19k9f1zH_9Q;3Ae+KlEpvB+i@b#Rp zhSqv+r?qmE9NuF1Kc}_pbd{9~at z|9Q?h(vy>*wVwCW+HnHtvrPZJzf{}fOWq>v@60e-iW~p*8;NXx)7| z!tg)g^p_sK!tlT8@b$dA4F3UNuIG*5f6nkd_||X8PJa$x>*+cC6#5Wot>i=Vh z|NGDv8ve7sQm^Nw@V^0H3=f(*FkIiCtp+V*D=u7nf`O| z_q*rhVw`>99|Wy=S81)>j)gwh@c)X|?yvU!G-!=~743;8Zpw~cZTNT4+Wmbp{EtIx z{D=N?&6jaXg9G(OH zG1LFK@a=OQ3ID%M|1o^K9@%vVTJ!!jt$i-xpEC^q1GH}ZeJ!-+y`9#MBl+v|8fWg> zdOh0T8x4O2zU_ZB{6|4+oOe5X`IFP2HO{AK?RpM_exvFC6Q{rY>cxhCkJJAV@UMr~ zIER0&o>%_lNATsh&xCK~^GxUiq1FG-XssXcp-(XUFFAbS*t4NE&TX{zxr9SLV*1}j zYwbw-{|&=`+SluO<*#mmuW@pRFF*50XvydAY3=wgf_^@<)_J$Xm;ZdH>HmOl)crN? zt%iRze7l|_;r|@I{=N;qm9zAA4qEfRmDb8f_~Chm|9Pjs_Ul#9TF*~u?K7>VMev_4ubkUuOC*Ied-t zU}%kVg~ONMJK6AmN^AE$g#Q6(jsMge>hXnhZh|kl_2AoeYQG*1E&e66cAc`{6QMQl z4Guqme!Jm+pVscnL*RcFTJ!!Jt(E8g&{v!O@s0Jo8eik8|105Je{tuo0((`vh7xR9r zeqQ;*uNr;=-_9$4dpms1a}<0#{sGX3L2KUQopJVqek!!ae>trk=TXp+>Hl_GJI)dC zpJV!eo7V2T{P3Te{*V54-T&F}zhL@@@a=wS{C%Od&NFH4yaDuMptYWNIef*P6QH&J zD{1XG!gc4H{y%W~3om`p@c+ZVTYV6&xXbiE&gn0E z+#g!&yujhhUIN3vj@HUY{GHI6ci->S&#V3VjOpKT_>#kan*JmBR{rvzkAv1Ym(p5$ zQ5-qL@E`Quy8r3$-vwXmTyXf(&tIDU?}2aSuklZUR{sr$--W)w@Spv?y1(@HHp5?c z_#ylNTI;-w)~-`=_DsXS)##}p4F5$AUw&gBX!XC0*2-r)^fL|r zZilaUdXeGp^Zj}~(uZ5%Yn?~Iw{kcV`VP}Sclt};TF~l$w$op6^96?gWm>!MFNWVU z{QYjO*LghrkHXjbPnzK0W%y^ow|XwTdIWSKPg=Xqli{CY_z(F(J@0AocNzYv@NIwj zy}RLS-t!&4;?yzF;$QCYCI5A3&3hH?y1|+1cd_Ze&kyVIg$F)v_@~3Sa!|f-2Yku< zjqvS$#n8`&)_A*Ut)3hTy>9pyJAC2NH$!VZzoNCzrF`%&O#kCv=AL*7|?t@FmZWoBq$awO)_vOFuUJ75H{u z=~WD^^;|a5|1`tD+u_R}?t<35Pyg3?eDQBJ{GAS8xbbjk^}opB3vbLD{&yX|;`N)L zwVs22RF8io^p_3)Xoo)s|9;RK|4dr@T!%ro4gVuff9dCQp*8-Uv{oM!FWzPN$NadS z_l59phOhBg9DWPhgBJh&4qy9wDzwJ`qQgHF`a;wHmkvLIf2ry3-&X6b - - @for (field of fields(); track field) { - - {{ colInfo()[field.propName()] }} - - } - - - @for (row of pagedData(); track $index) { - - @for (field of fields(); track field) { - - @if (row) { - - } @else { -   - } - - } - - } - - - diff --git a/src/app/domains/shared/ui-common/advanced-data-table/advanced-data-table.ts b/src/app/domains/shared/ui-common/advanced-data-table/advanced-data-table.ts deleted file mode 100644 index 066435bd..00000000 --- a/src/app/domains/shared/ui-common/advanced-data-table/advanced-data-table.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { CommonModule, NgTemplateOutlet } from '@angular/common'; -import { - Component, - computed, - contentChildren, - input, - signal, -} from '@angular/core'; - -import { TableField } from '../data-table/table-field'; - -type SortDirection = 'asc' | 'desc'; - -interface SortState { - column: keyof T; - direction: SortDirection; -} - -@Component({ - selector: 'app-data-table', - imports: [CommonModule, NgTemplateOutlet], - templateUrl: './advanced-data-table.html', - styleUrl: './advanced-data-table.css', -}) -export class DataTable { - readonly data = input([]); - readonly pageSize = input(2); - - protected readonly fields = contentChildren>(TableField); - - protected readonly sortState = signal>({ - column: 'id' as keyof T, - direction: 'asc', - }); - - protected readonly currentPage = signal(0); - - readonly sortedData = computed(() => sortData(this.data(), this.sortState())); - - protected readonly colInfo = computed(() => - buildColumnInfo(this.fields(), this.sortState()), - ); - - readonly pagedData = computed(() => - paginateData(this.sortedData(), this.currentPage(), this.pageSize()), - ); - - protected readonly totalPages = computed(() => - Math.ceil(this.sortedData().length / this.pageSize()), - ); - - protected sort(column: keyof T): void { - const current = this.sortState(); - - if (current.column === column) { - this.sortState.set({ - column, - direction: current.direction === 'asc' ? 'desc' : 'asc', - }); - } else { - this.sortState.set({ column, direction: 'asc' }); - } - - this.currentPage.set(0); - } - - protected previousPage(): void { - const current = this.currentPage(); - if (current > 0) { - this.currentPage.set(current - 1); - } - } - - protected nextPage(): void { - const current = this.currentPage(); - if (current < this.totalPages() - 1) { - this.currentPage.set(current + 1); - } - } -} - -function sortData(data: T[], sortState: SortState): T[] { - return [...data].sort((a, b) => { - const aVal = a[sortState.column]; - const bVal = b[sortState.column]; - - let comparison = 0; - if (aVal > bVal) { - comparison = 1; - } else if (aVal < bVal) { - comparison = -1; - } - - return sortState.direction === 'asc' ? comparison : -comparison; - }); -} - -function buildColumnInfo( - fields: readonly TableField[], - sortState: SortState, -): Record { - const result: Record = {}; - - for (const field of fields) { - const propName = field.propName(); - const title = field.title(); - - if (sortState.column === propName) { - const arrow = sortState.direction === 'asc' ? '↑' : '↓'; - result[propName as PropertyKey] = `${String(title)} ${arrow}`; - } else { - result[propName as PropertyKey] = `${String(title)} `; - } - } - - return result; -} - -function paginateData( - data: T[], - page: number, - pageSize: number, -): (T | null)[] { - const start = page * pageSize; - const pageData: (T | null)[] = data.slice(start, start + pageSize); - - while (pageData.length < pageSize) { - pageData.push(null); - } - - return pageData; -} diff --git a/src/app/domains/shared/ui-common/click-with-warning.ts b/src/app/domains/shared/ui-common/click-with-warning.ts deleted file mode 100644 index 46f8b973..00000000 --- a/src/app/domains/shared/ui-common/click-with-warning.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Dialog } from '@angular/cdk/dialog'; -import { Directive, inject, input, output } from '@angular/core'; - -import { ConfirmComponent } from '../util-common/confirm'; - -@Directive({ - selector: '[appClickWithWarning]', - exportAs: 'clickWithWarning', - host: { - class: 'btn btn-danger', - '(click)': 'handleClick($event.shiftKey)', - }, -}) -export class ClickWithWarning { - private readonly dialog = inject(Dialog); - - readonly warning = input('Are you sure?'); - readonly appClickWithWarning = output(); - - handleClick(shiftKey: boolean): void { - if (shiftKey) { - this.appClickWithWarning.emit(); - return; - } - - const ref = this.dialog.open(ConfirmComponent, { - data: this.warning(), - }); - ref.closed.subscribe((result) => { - if (result) { - this.appClickWithWarning.emit(); - } - }); - } -} diff --git a/src/app/domains/shared/ui-common/custom-template-outlet.ts b/src/app/domains/shared/ui-common/custom-template-outlet.ts deleted file mode 100644 index 9f53edc2..00000000 --- a/src/app/domains/shared/ui-common/custom-template-outlet.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - Directive, - effect, - inject, - input, - TemplateRef, - ViewContainerRef, -} from '@angular/core'; - -@Directive({ - selector: '[appCustomTemplateOutlet]', -}) -export class CustomTemplateOutlet { - readonly template = input | undefined>(undefined, { - alias: 'appCustomTemplateOutlet', - }); - readonly context = input(undefined, { - alias: 'appCustomTemplateOutletContext', - }); - - private readonly viewContainer = inject(ViewContainerRef); - - constructor() { - effect(() => { - const tmpl = this.template(); - const ctx = this.context(); - - if (!tmpl) { - return; - } - - this.viewContainer.clear(); - this.viewContainer.createEmbeddedView(tmpl, ctx); - }); - } -} diff --git a/src/app/domains/shared/ui-common/data-table/data-table.ts b/src/app/domains/shared/ui-common/data-table/data-table.ts deleted file mode 100644 index 7804c4b1..00000000 --- a/src/app/domains/shared/ui-common/data-table/data-table.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { NgTemplateOutlet } from '@angular/common'; -import { Component, contentChildren, input } from '@angular/core'; - -// import { CustomTemplateOutlet } from '../custom-template-outlet'; -import { TableField } from './table-field'; - -@Component({ - selector: 'app-data-table', - imports: [NgTemplateOutlet], - template: ` - - - @for (field of fields(); track field) { - - } - - - @for (row of data(); track row) { - - @for (field of fields(); track field) { - - } - - } -
- {{ field.title() }} -
- -
- `, -}) -export class DataTable { - readonly data = input([]); - protected readonly fields = contentChildren>(TableField); -} diff --git a/src/app/domains/shared/ui-common/data-table/table-field.ts b/src/app/domains/shared/ui-common/data-table/table-field.ts deleted file mode 100644 index 8e414790..00000000 --- a/src/app/domains/shared/ui-common/data-table/table-field.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Directive, inject, input, TemplateRef } from '@angular/core'; - -@Directive({ - selector: '[appTableField]', -}) -export class TableField { - readonly propName = input.required({ - // eslint-disable-next-line @angular-eslint/no-input-rename - alias: 'appTableFieldProvide', - }); - readonly title = input.required({ alias: 'appTableFieldTitle' }); - readonly templateRef = inject(TemplateRef) as TemplateRef; -} diff --git a/src/app/domains/shared/ui-common/dialog/dialog-event.ts b/src/app/domains/shared/ui-common/dialog/dialog-event.ts deleted file mode 100644 index f74f923e..00000000 --- a/src/app/domains/shared/ui-common/dialog/dialog-event.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Type } from '@angular/core'; - -export interface DialogEvent { - component: Type | null; - data: unknown; -} diff --git a/src/app/domains/shared/ui-common/dialog/dialog-info.ts b/src/app/domains/shared/ui-common/dialog/dialog-info.ts deleted file mode 100644 index f74f923e..00000000 --- a/src/app/domains/shared/ui-common/dialog/dialog-info.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Type } from '@angular/core'; - -export interface DialogEvent { - component: Type | null; - data: unknown; -} diff --git a/src/app/domains/shared/ui-common/dialog/dialog-outlet-service.ts b/src/app/domains/shared/ui-common/dialog/dialog-outlet-service.ts deleted file mode 100644 index 4d438926..00000000 --- a/src/app/domains/shared/ui-common/dialog/dialog-outlet-service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - ApplicationRef, - ComponentRef, - createComponent, - DestroyRef, - DOCUMENT, - EnvironmentInjector, - inject, - Injectable, -} from '@angular/core'; - -import { DialogOutlet } from './dialog-outlet'; - -@Injectable({ providedIn: 'root' }) -export class DialogOutletService { - private envInjector = inject(EnvironmentInjector); - private appRef = inject(ApplicationRef); - private document = inject(DOCUMENT); - private destroyRef = inject(DestroyRef); - - private componentRef: ComponentRef | null = null; - - constructor() { - this.destroyRef.onDestroy(() => { - this.componentRef?.destroy(); - }); - } - - ensureOutlet() { - if (!this.componentRef) { - this.componentRef = createComponent(DialogOutlet, { - environmentInjector: this.envInjector, - }); - - this.appRef.attachView(this.componentRef.hostView); - this.document.body.appendChild(this.componentRef.location.nativeElement); - } - } -} diff --git a/src/app/domains/shared/ui-common/dialog/dialog-outlet.css b/src/app/domains/shared/ui-common/dialog/dialog-outlet.css deleted file mode 100644 index 303632c0..00000000 --- a/src/app/domains/shared/ui-common/dialog/dialog-outlet.css +++ /dev/null @@ -1,29 +0,0 @@ -.background { - opacity: 0.4; - background-color: black; - left: 0px; - top: 0px; - width: 100%; - height: 100%; - position: fixed; - z-index: 100; -} - -.container { - left: 0px; - top: 0px; - width: 100%; - height: 100%; - position: fixed; - z-index: 110; -} - -.dialog { - background-color: white; - margin-top: 100px; - margin-left: auto; - margin-right: auto; - width: 400px; - position: relative; - z-index: 120; -} diff --git a/src/app/domains/shared/ui-common/dialog/dialog-outlet.html b/src/app/domains/shared/ui-common/dialog/dialog-outlet.html deleted file mode 100644 index fca630f7..00000000 --- a/src/app/domains/shared/ui-common/dialog/dialog-outlet.html +++ /dev/null @@ -1,12 +0,0 @@ -@let componentInstance = component(); -@let injectorInstance = injector(); - -@if (componentInstance && injectorInstance) { -
-
-
- -
-
-} diff --git a/src/app/domains/shared/ui-common/dialog/dialog-outlet.ts b/src/app/domains/shared/ui-common/dialog/dialog-outlet.ts deleted file mode 100644 index f907b784..00000000 --- a/src/app/domains/shared/ui-common/dialog/dialog-outlet.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { NgComponentOutlet } from '@angular/common'; -import { - Component, - DestroyableInjector, - inject, - Injector, - signal, - Type, -} from '@angular/core'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; - -import { DialogService } from './dialog.service'; -import { DIALOG_DATA } from './dialog.token'; -import { DialogEvent } from './dialog-event'; - -@Component({ - selector: 'app-dialog-outlet', - imports: [NgComponentOutlet], - templateUrl: './dialog-outlet.html', - styleUrl: './dialog-outlet.css', -}) -export class DialogOutlet { - private readonly dialogService = inject(DialogService); - private readonly parentInjector = inject(Injector); - - protected component = signal | null>(null); - protected injector = signal(null); - - constructor() { - this.dialogService.dialogEvents$ - .pipe(takeUntilDestroyed()) - .subscribe((event) => { - this.processEvent(event); - }); - } - - private processEvent(event: DialogEvent): void { - const injector = this.injector(); - if (injector) { - injector.destroy(); - this.injector.set(null); - } - - if (!event.component) { - this.component.set(null); - return; - } - - this.component.set(event.component); - - const newInjector = Injector.create({ - providers: [{ provide: DIALOG_DATA, useValue: event.data }], - parent: this.parentInjector, - }); - this.injector.set(newInjector); - } -} diff --git a/src/app/domains/shared/ui-common/dialog/dialog.service.ts b/src/app/domains/shared/ui-common/dialog/dialog.service.ts deleted file mode 100644 index 606a5867..00000000 --- a/src/app/domains/shared/ui-common/dialog/dialog.service.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { inject, Injectable, Type } from '@angular/core'; -import { Subject } from 'rxjs'; - -import { DialogEvent } from './dialog-event'; -import { DialogOutletService } from './dialog-outlet-service'; - -@Injectable({ - providedIn: 'root', -}) -export class DialogService { - private dialogOutletService = inject(DialogOutletService); - - private readonly dialogEvents = new Subject(); - readonly dialogEvents$ = this.dialogEvents.asObservable(); - - show(comp: Type, data: unknown): void { - this.dialogOutletService.ensureOutlet(); - this.dialogEvents.next({ - component: comp, - data, - }); - } - - hide(): void { - this.dialogEvents.next({ - component: null, - data: null, - }); - } -} diff --git a/src/app/domains/shared/ui-common/dialog/dialog.token.ts b/src/app/domains/shared/ui-common/dialog/dialog.token.ts deleted file mode 100644 index 821b0349..00000000 --- a/src/app/domains/shared/ui-common/dialog/dialog.token.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { InjectionToken } from '@angular/core'; - -export const DIALOG_DATA = new InjectionToken('DIALOG_DATA'); diff --git a/src/app/domains/shared/ui-common/injection-tabbed-pane/tab.ts b/src/app/domains/shared/ui-common/injection-tabbed-pane/tab.ts deleted file mode 100644 index 955f0cab..00000000 --- a/src/app/domains/shared/ui-common/injection-tabbed-pane/tab.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Component, computed, inject, input } from '@angular/core'; - -import { TabbedPane } from './tabbed-pane'; - -@Component({ - selector: 'app-tab', - imports: [], - template: ` - @if (visible()) { -
- -
- } - `, - styles: ` - .tab-content { - animation: fadeIn 0.2s ease-in; - } - - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - `, -}) -export class Tab { - // private pane = inject(TabbedPane, { optional: true }); - private pane = inject(TabbedPane); - readonly title = input.required(); - - protected readonly visible = computed(() => this.pane.currentTab() === this); - - constructor() { - this.pane.registerTab(this); - } -} diff --git a/src/app/domains/shared/ui-common/injection-tabbed-pane/tabbed-pane.ts b/src/app/domains/shared/ui-common/injection-tabbed-pane/tabbed-pane.ts deleted file mode 100644 index 7679d92d..00000000 --- a/src/app/domains/shared/ui-common/injection-tabbed-pane/tabbed-pane.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Component, computed, model, signal } from '@angular/core'; - -import { Tab } from './tab'; - -@Component({ - selector: 'app-tabbed-pane', - imports: [], - template: ` -
-
- @for (tab of tabs(); track tab) { - - } -
-
- -
-
- `, - styles: ` - .pane { - display: flex; - flex-direction: column; - box-sizing: border-box; - width: 100%; - padding: 25px; - background-color: #fff; - border: 1px solid #e0e0e0; - border-radius: 6px; - } - - .tabs-header { - display: flex; - gap: 8px; - border-bottom: 1px solid #e0e0e0; - } - - .tab-button { - padding: 10px 20px; - border: none; - border-radius: 0; - background: transparent; - color: #666; - font-size: 14px; - cursor: pointer; - transition: color 0.2s ease; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - } - - .tab-button:hover { - color: #333; - } - - .tab-button.active { - color: #1976d2; - border-bottom: 2px solid #1976d2; - border-radius: 0; - } - - .tabs-content { - padding: 20px; - background-color: #fff; - margin-top: 0; - } - `, -}) -export class TabbedPane { - protected readonly current = model(0); - protected readonly tabs = signal([]); - - readonly currentTab = computed(() => this.tabs()[this.current()]); - - registerTab(tab: Tab): void { - this.tabs.update((tabs) => [...tabs, tab]); - } - - activate(tabIndex: number): void { - this.current.set(tabIndex); - } -} diff --git a/src/app/domains/shared/ui-common/query-tabbed-pane/tab.ts b/src/app/domains/shared/ui-common/query-tabbed-pane/tab.ts deleted file mode 100644 index 0a3847d7..00000000 --- a/src/app/domains/shared/ui-common/query-tabbed-pane/tab.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Component, computed, input } from '@angular/core'; - -import { TabbedPane } from './tabbed-pane'; - -@Component({ - selector: 'app-tab', - imports: [], - template: ` - @if (visible()) { -
- -
- } - `, - styles: ` - .tab-content { - animation: fadeIn 0.2s ease-in; - } - - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - `, -}) -export class Tab { - // private pane = inject(TabbedPane, { optional: true }); - private _pane?: TabbedPane; - readonly title = input.required(); - - protected readonly visible = computed( - () => this._pane?.currentTab() === this, - ); - - set pane(pane: TabbedPane) { - this._pane = pane; - } -} diff --git a/src/app/domains/shared/ui-common/query-tabbed-pane/tabbed-pane.ts b/src/app/domains/shared/ui-common/query-tabbed-pane/tabbed-pane.ts deleted file mode 100644 index 10d997e2..00000000 --- a/src/app/domains/shared/ui-common/query-tabbed-pane/tabbed-pane.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { - Component, - computed, - contentChildren, - effect, - model, -} from '@angular/core'; - -import { Tab } from './tab'; - -@Component({ - selector: 'app-tabbed-pane', - imports: [], - template: ` -
-
- @for (tab of tabs(); track tab) { - - } -
-
- -
-
- `, - styles: ` - .pane { - display: flex; - flex-direction: column; - box-sizing: border-box; - width: 100%; - padding: 25px; - background-color: #fff; - border: 1px solid #e0e0e0; - border-radius: 6px; - margin-bottom: 20px; - } - - .tabs-header { - display: flex; - gap: 8px; - border-bottom: 1px solid #e0e0e0; - } - - .tab-button { - padding: 10px 20px; - border: none; - border-radius: 0; - background: transparent; - color: #666; - font-size: 14px; - cursor: pointer; - transition: color 0.2s ease; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - } - - .tab-button:hover { - color: #333; - } - - .tab-button.active { - color: #1976d2; - border-bottom: 2px solid #1976d2; - border-radius: 0; - } - - .tabs-content { - padding: 20px; - background-color: #fff; - margin-top: 0; - } - `, -}) -export class TabbedPane { - protected readonly current = model(0); - protected readonly tabs = contentChildren(Tab); - - // protected readonly tabs = contentChildren('tab'); - // protected readonly tabs = contentChildren(Tab, { descendants: true }); - - readonly currentTab = computed(() => this.tabs()[this.current()]); - - constructor() { - effect(() => { - for (const tab of this.tabs()) { - tab.pane = this; - } - }); - } - - activate(tabIndex: number): void { - this.current.set(tabIndex); - } -} - -// Further options - -// protected readonly tabElements = contentChildren(Tab, { read: ElementRef }); -// protected readonly tabViewContainers = contentChildren(Tab, { read: ViewContainerRef }); - -// for (const element of this.tabElements()) { -// element.nativeElement.style.color = 'green'; -// } - -// for (const container of this.tabViewContainers()) { -// container.createComponent(Home); -// } diff --git a/src/app/domains/shared/ui-common/service-tabbed-pane/tab-registry.ts b/src/app/domains/shared/ui-common/service-tabbed-pane/tab-registry.ts deleted file mode 100644 index cfef54ad..00000000 --- a/src/app/domains/shared/ui-common/service-tabbed-pane/tab-registry.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { computed, Injectable, Signal, signal } from '@angular/core'; - -export interface TabInfo { - title: Signal; -} - -// No { providedIn: 'root' }! -// This service is provided in the tabbed-pane -@Injectable() -export class TabRegistry { - private readonly _current = signal(0); - private readonly _tabs = signal([]); - - readonly current = this._current.asReadonly(); - readonly tabs = this._tabs.asReadonly(); - readonly currentTab = computed(() => this.tabs()[this.current()]); - - registerTab(tab: TabInfo): void { - this._tabs.update((tabs) => [...tabs, tab]); - } - - activate(tabIndex: number): void { - this._current.set(tabIndex); - } -} diff --git a/src/app/domains/shared/ui-common/service-tabbed-pane/tab.ts b/src/app/domains/shared/ui-common/service-tabbed-pane/tab.ts deleted file mode 100644 index 2745795d..00000000 --- a/src/app/domains/shared/ui-common/service-tabbed-pane/tab.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Component, computed, inject, input } from '@angular/core'; - -import { TabInfo, TabRegistry } from './tab-registry'; - -@Component({ - selector: 'app-tab', - imports: [], - template: ` - @if (visible()) { -
- -
- } - `, - styles: ` - .tab-content { - animation: fadeIn 0.2s ease-in; - } - - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - `, -}) -export class Tab implements TabInfo { - private registry = inject(TabRegistry); - readonly title = input.required(); - - protected readonly visible = computed( - () => this.registry.currentTab() === this, - ); - - constructor() { - this.registry.registerTab(this); - } -} diff --git a/src/app/domains/shared/ui-common/service-tabbed-pane/tabbed-pane.ts b/src/app/domains/shared/ui-common/service-tabbed-pane/tabbed-pane.ts deleted file mode 100644 index 26c0c052..00000000 --- a/src/app/domains/shared/ui-common/service-tabbed-pane/tabbed-pane.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Component, inject } from '@angular/core'; - -import { TabRegistry } from './tab-registry'; - -@Component({ - selector: 'app-tabbed-pane', - providers: [TabRegistry], - imports: [], - template: ` -
-
- @for (tab of tabs(); track tab) { - - } -
-
- -
-
- `, - styles: ` - .pane { - display: flex; - flex-direction: column; - box-sizing: border-box; - width: 100%; - padding: 25px; - background-color: #fff; - border: 1px solid #e0e0e0; - border-radius: 6px; - } - - .tabs-header { - display: flex; - gap: 8px; - border-bottom: 1px solid #e0e0e0; - } - - .tab-button { - padding: 10px 20px; - border: none; - border-radius: 0; - background: transparent; - color: #666; - font-size: 14px; - cursor: pointer; - transition: color 0.2s ease; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - } - - .tab-button:hover { - color: #333; - } - - .tab-button.active { - color: #1976d2; - border-bottom: 2px solid #1976d2; - border-radius: 0; - } - - .tabs-content { - padding: 20px; - background-color: #fff; - margin-top: 0; - } - `, -}) -export class TabbedPane { - protected readonly registry = inject(TabRegistry); - protected readonly tabs = this.registry.tabs; - protected readonly currentTab = this.registry.currentTab; - - activate(tabIndex: number): void { - this.registry.activate(tabIndex); - } -} diff --git a/src/app/domains/shared/ui-common/simple-tooltip.ts b/src/app/domains/shared/ui-common/simple-tooltip.ts deleted file mode 100644 index f6ec28b7..00000000 --- a/src/app/domains/shared/ui-common/simple-tooltip.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - afterRenderEffect, - DestroyRef, - Directive, - ElementRef, - inject, - input, -} from '@angular/core'; - -@Directive({ - selector: '[appSimpleTooltip]', - host: { - '(mouseover)': 'setHidden(false)', - '(mouseout)': 'setHidden(true)', - }, -}) -export class SimpleTooltip { - private readonly host = inject(ElementRef); - private tooltipElement: HTMLElement | null = null; - private readonly destroyRef = inject(DestroyRef); - - readonly tooltipText = input.required({ alias: 'appSimpleTooltip' }); - - constructor() { - afterRenderEffect(() => { - const text = this.tooltipText(); - this.initTooltip(); - this.updateText(text); - }); - - this.destroyRef.onDestroy(() => { - this.removeTooltip(); - }); - } - - setHidden(hidden: boolean): void { - if (this.tooltipElement) { - if (!hidden) { - this.updatePosition(this.tooltipElement); - } - this.tooltipElement.hidden = hidden; - } - } - - private initTooltip(): void { - if (this.tooltipElement) { - return; - } - - this.tooltipElement = document.createElement('div'); - this.tooltipElement.className = 'tooltip'; - this.tooltipElement.hidden = true; - document.body.appendChild(this.tooltipElement); - } - - private updatePosition(tooltipElement: HTMLElement) { - const rect = this.host.nativeElement.getBoundingClientRect(); - tooltipElement.style.left = `${rect.left + rect.width / 2}px`; - tooltipElement.style.top = `${rect.top - 8}px`; - tooltipElement.style.transform = 'translate(-50%, -100%)'; - } - - private updateText(tooltipText: string): void { - if (this.tooltipElement) { - this.tooltipElement.textContent = tooltipText; - } - } - - private removeTooltip() { - if (this.tooltipElement && this.tooltipElement.parentNode) { - this.tooltipElement.parentNode.removeChild(this.tooltipElement); - this.tooltipElement = null; - } - } -} diff --git a/src/app/domains/shared/ui-common/tooltip.ts b/src/app/domains/shared/ui-common/tooltip.ts deleted file mode 100644 index 3ff1265e..00000000 --- a/src/app/domains/shared/ui-common/tooltip.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - afterRenderEffect, - DestroyRef, - Directive, - ElementRef, - EmbeddedViewRef, - inject, - input, - TemplateRef, - ViewContainerRef, -} from '@angular/core'; - -interface TooltipContext { - $implicit: string; - text: string; -} - -@Directive({ - selector: '[appTooltip]', - host: { - '(mouseover)': 'setHidden(false)', - '(mouseout)': 'setHidden(true)', - }, -}) -export class Tooltip { - private readonly viewContainer = inject(ViewContainerRef); - private viewRef: EmbeddedViewRef | undefined; - private host = inject(ElementRef); - private readonly destroyRef = inject(DestroyRef); - - readonly template = input | undefined>( - undefined, - { alias: 'appTooltip' }, - ); - - constructor() { - afterRenderEffect(() => { - const template = this.template(); - if (!template) { - return; - } - this.initTooltip(template); - }); - - this.destroyRef.onDestroy(() => { - this.removeTooltip(); - }); - } - - private removeTooltip() { - if (this.viewRef) { - this.viewRef.destroy(); - this.viewRef = undefined; - } - } - - initTooltip(template: TemplateRef): void { - if (this.viewRef) { - this.viewRef.destroy(); - } - - this.viewRef = this.viewContainer.createEmbeddedView(template, { - $implicit: 'Tooltip!', - text: 'Important Information!', - }); - - this.viewRef?.rootNodes.forEach((nativeElement) => { - nativeElement.className = 'tooltip'; - nativeElement.hidden = true; - }); - } - - setHidden(hidden: boolean): void { - this.viewRef?.rootNodes.forEach((nativeElement) => { - if (!hidden) { - this.updatePosition(nativeElement); - } - nativeElement.hidden = hidden; - }); - } - - private updatePosition(toolTipElement: HTMLElement) { - const r = this.host.nativeElement.getBoundingClientRect(); - toolTipElement.style.left = `${r.left + r.width / 2}px`; - toolTipElement.style.top = `${r.top - 8}px`; - toolTipElement.style.transform = 'translate(-50%, -100%)'; - } -} diff --git a/src/app/shell/about/about.html b/src/app/shell/about/about.html index 7a54224a..5c337103 100644 --- a/src/app/shell/about/about.html +++ b/src/app/shell/about/about.html @@ -1,102 +1,3 @@

About

- - -

No upcoming flights!

-
- - -

No operated flights!

-
- - -

No cancelled flights!

-
-
- -

 

- - - -  - - - -  - - - - - - - -

 

- -
- -
- -

 

- -
- -
- - -
-

{{ title }}

-

- {{ body }} -

-
-
- -

 

- - -
- {{ data }} -
-
- {{ data }} -
-
{{ data }}
-
- {{ data | date: 'dd.MM.yyyy HH:mm' }} -
-
- -

 

- - - +

by AngularArchitects.io

diff --git a/src/app/shell/about/about.ts b/src/app/shell/about/about.ts index f17a166a..69d93077 100644 --- a/src/app/shell/about/about.ts +++ b/src/app/shell/about/about.ts @@ -1,40 +1,13 @@ -import { DatePipe } from '@angular/common'; -import { - ChangeDetectionStrategy, - Component, - inject, - signal, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; -import { DataTable } from '../../domains/shared/ui-common/advanced-data-table/advanced-data-table'; -import { ClickWithWarning } from '../../domains/shared/ui-common/click-with-warning'; -import { TableField } from '../../domains/shared/ui-common/data-table/table-field'; -import { DialogService } from '../../domains/shared/ui-common/dialog/dialog.service'; -import { Tab } from '../../domains/shared/ui-common/service-tabbed-pane/tab'; -import { TabbedPane } from '../../domains/shared/ui-common/service-tabbed-pane/tabbed-pane'; -import { SimpleTooltip } from '../../domains/shared/ui-common/simple-tooltip'; -import { Tooltip } from '../../domains/shared/ui-common/tooltip'; import { Flight } from '../../domains/ticketing/data/flight'; -import { DemoDialog } from './demo-dialog'; @Component({ selector: 'app-about', - imports: [ - Tab, - TabbedPane, - ClickWithWarning, - Tooltip, - SimpleTooltip, - TableField, - DatePipe, - DataTable, - ], templateUrl: './about.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class About { - private readonly dialogService = inject(DialogService); - protected readonly flights = signal([ { id: 1, @@ -67,12 +40,4 @@ export class About { prices: [], }, ]); - - protected deleteAll(): void { - console.debug('delete ...'); - } - - protected showDialog(): void { - this.dialogService.show(DemoDialog, 'Hello from About Component!'); - } } diff --git a/src/app/shell/about/demo-dialog.ts b/src/app/shell/about/demo-dialog.ts deleted file mode 100644 index 789e3942..00000000 --- a/src/app/shell/about/demo-dialog.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Component, inject } from '@angular/core'; - -import { DialogService } from '../../domains/shared/ui-common/dialog/dialog.service'; -import { DIALOG_DATA } from '../../domains/shared/ui-common/dialog/dialog.token'; - -@Component({ - selector: 'app-demo-dialog', - template: ` -
-
-

Message

-

{{ message }}

- -
-
- `, -}) -export class DemoDialog { - private readonly data = inject(DIALOG_DATA); - private readonly dialogService = inject(DialogService); - - protected readonly message = this.data as string; - - close(): void { - this.dialogService.hide(); - } -} From 440e23fa0b7d41e17f1bf33b62d7e1b3dd2cc03d Mon Sep 17 00:00:00 2001 From: Hakan <112955732+InalHakan@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:31:42 +0200 Subject: [PATCH 2/2] feat: implement directives exercise --- angular.json | 3 +- .../shared/ui-common/click-with-warning.ts | 35 ++++++++ src/app/domains/shared/ui-common/tooltip.ts | 79 +++++++++++++++++++ src/app/shell/about/about.html | 24 ++++++ src/app/shell/about/about.ts | 7 ++ 5 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/app/domains/shared/ui-common/click-with-warning.ts create mode 100644 src/app/domains/shared/ui-common/tooltip.ts diff --git a/angular.json b/angular.json index 83193047..bb2d4681 100644 --- a/angular.json +++ b/angular.json @@ -3,7 +3,8 @@ "version": 1, "cli": { "packageManager": "npm", - "schematicCollections": ["angular-eslint"] + "schematicCollections": ["angular-eslint"], + "analytics": "955b7c74-ae29-4025-8907-29a55529de8a" }, "newProjectRoot": "projects", "projects": { diff --git a/src/app/domains/shared/ui-common/click-with-warning.ts b/src/app/domains/shared/ui-common/click-with-warning.ts new file mode 100644 index 00000000..46f8b973 --- /dev/null +++ b/src/app/domains/shared/ui-common/click-with-warning.ts @@ -0,0 +1,35 @@ +import { Dialog } from '@angular/cdk/dialog'; +import { Directive, inject, input, output } from '@angular/core'; + +import { ConfirmComponent } from '../util-common/confirm'; + +@Directive({ + selector: '[appClickWithWarning]', + exportAs: 'clickWithWarning', + host: { + class: 'btn btn-danger', + '(click)': 'handleClick($event.shiftKey)', + }, +}) +export class ClickWithWarning { + private readonly dialog = inject(Dialog); + + readonly warning = input('Are you sure?'); + readonly appClickWithWarning = output(); + + handleClick(shiftKey: boolean): void { + if (shiftKey) { + this.appClickWithWarning.emit(); + return; + } + + const ref = this.dialog.open(ConfirmComponent, { + data: this.warning(), + }); + ref.closed.subscribe((result) => { + if (result) { + this.appClickWithWarning.emit(); + } + }); + } +} diff --git a/src/app/domains/shared/ui-common/tooltip.ts b/src/app/domains/shared/ui-common/tooltip.ts new file mode 100644 index 00000000..3e037ca1 --- /dev/null +++ b/src/app/domains/shared/ui-common/tooltip.ts @@ -0,0 +1,79 @@ +import { + afterRenderEffect, + DestroyRef, + Directive, + ElementRef, + EmbeddedViewRef, + inject, + input, + TemplateRef, + ViewContainerRef, +} from '@angular/core'; + +@Directive({ + selector: '[appTooltip]', + host: { + '(mouseover)': 'setHidden(false)', + '(mouseout)': 'setHidden(true)', + }, +}) +export class Tooltip { + private readonly viewContainer = inject(ViewContainerRef); + private viewRef: EmbeddedViewRef | undefined; + private readonly host = inject(ElementRef); + private readonly destroyRef = inject(DestroyRef); + + readonly template = input | undefined>(undefined, { + alias: 'appTooltip', + }); + + constructor() { + afterRenderEffect(() => { + const template = this.template(); + if (!template) { + return; + } + this.initTooltip(template); + }); + + this.destroyRef.onDestroy(() => { + this.removeTooltip(); + }); + } + + private removeTooltip() { + if (this.viewRef) { + this.viewRef.destroy(); + this.viewRef = undefined; + } + } + + initTooltip(template: TemplateRef): void { + if (this.viewRef) { + this.viewRef.destroy(); + } + + this.viewRef = this.viewContainer.createEmbeddedView(template); + + this.viewRef?.rootNodes.forEach((nativeElement) => { + nativeElement.className = 'tooltip'; + nativeElement.hidden = true; + }); + } + + setHidden(hidden: boolean): void { + this.viewRef?.rootNodes.forEach((nativeElement) => { + if (!hidden) { + this.updatePosition(nativeElement); + } + nativeElement.hidden = hidden; + }); + } + + private updatePosition(toolTipElement: HTMLElement) { + const r = this.host.nativeElement.getBoundingClientRect(); + toolTipElement.style.left = `${r.left + r.width / 2}px`; + toolTipElement.style.top = `${r.top - 8}px`; + toolTipElement.style.transform = 'translate(-50%, -100%)'; + } +} diff --git a/src/app/shell/about/about.html b/src/app/shell/about/about.html index 5c337103..a66ed393 100644 --- a/src/app/shell/about/about.html +++ b/src/app/shell/about/about.html @@ -1,3 +1,27 @@

About

by AngularArchitects.io

+
+ +
+ + +
+

2 Tips for Success

+
    +
  1. Don't tell everything!
  2. +
+
+
+
+ +

 

+ + diff --git a/src/app/shell/about/about.ts b/src/app/shell/about/about.ts index 69d93077..3c8451b8 100644 --- a/src/app/shell/about/about.ts +++ b/src/app/shell/about/about.ts @@ -1,9 +1,12 @@ import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; +import { ClickWithWarning } from '../../domains/shared/ui-common/click-with-warning'; +import { Tooltip } from '../../domains/shared/ui-common/tooltip'; import { Flight } from '../../domains/ticketing/data/flight'; @Component({ selector: 'app-about', + imports: [ClickWithWarning, Tooltip], templateUrl: './about.html', changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -40,4 +43,8 @@ export class About { prices: [], }, ]); + + protected deleteAll(): void { + console.log('Delete all flights'); + } }