diff --git a/apps/mix-cms/src/app/pages/portal/database/components/db-context-form/db-context-form.component.ts b/apps/mix-cms/src/app/pages/portal/database/components/db-context-form/db-context-form.component.ts index 773cec53..c7ab5738 100644 --- a/apps/mix-cms/src/app/pages/portal/database/components/db-context-form/db-context-form.component.ts +++ b/apps/mix-cms/src/app/pages/portal/database/components/db-context-form/db-context-form.component.ts @@ -21,7 +21,7 @@ import { MixSelectComponent } from '@mixcore/ui/select'; import { MixTextAreaComponent } from '@mixcore/ui/textarea'; import { DialogRef } from '@ngneat/dialog'; import { HotToastService } from '@ngneat/hot-toast'; -import { debounceTime, delay, tap } from 'rxjs'; +import { debounceTime, tap } from 'rxjs'; @Component({ selector: 'mix-db-context-form', @@ -58,7 +58,7 @@ export class DbContextFormComponent extends BaseComponent { public displayName$ = this.form.controls.displayName.valueChanges.pipe( takeUntilDestroyed(), - debounceTime(300), + debounceTime(600), tap(() => this.updateSystemName()) ); @@ -71,7 +71,7 @@ export class DbContextFormComponent extends BaseComponent { this.form.disable(); this.mixApi.databaseContext .save(this.form.value as MixDbContext) - .pipe(delay(5000), this.observerLoadingStateSignal()) + .pipe(this.observerLoadingStateSignal()) .subscribe({ next: () => { this.toast.success('Successfully create your db context'); diff --git a/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.html b/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.html index 13208232..423d47be 100644 --- a/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.html +++ b/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.html @@ -1,33 +1,82 @@
+ [title]="'All Db Context'">
add   New Context
- - delete   - Remove(s)
- @if (store.vm$ |async; as vm) { -
- @for (item of vm.data; track item.id) { -
-

- {{ item.displayName }} -

- {{ item.databaseProvider }} + @if (store.vm$ |async; as vm) { @if (vm.status === 'Loading') { +
+ +
+ } @else if (!vm.data.length) { +
+ +
+ } @else { +
+
Choose provider:
+ + + {{ ProviderLabel[item] }} + +
+ +
+
+
Available contexts:
+ +
+
+ @for (item of combineFilter(vm.data, filterForm.value); track item.id) + { +
+

+ {{ item.displayName }} +

+ {{ item.databaseProvider }} + + more_horiz +
+ }
- }
- } + } }
+ + + +
+
+ delete + Remove +
+
+
diff --git a/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.scss b/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.scss index 83c9d7a7..618778bc 100644 --- a/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.scss +++ b/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.scss @@ -1,10 +1,11 @@ .context-card { - padding: 1rem; + padding: 1rem 4rem 1rem 1rem; background: var(--tui-base-02); height: 8rem; min-width: 200px; width: fit-content; cursor: pointer; + position: relative; &:hover:after { transform: scale(1.1); @@ -17,17 +18,27 @@ &.sqlserver:after { background: url('/assets/icons/microsoft.svg') bottom right no-repeat; + background-size: 50px; } &.postgres:after { background: url('/assets/icons/postgres.svg') bottom right no-repeat; + background-size: 50px; } &.mysql:after { background: url('/assets/icons/mysql.svg') bottom right no-repeat; + background-size: 100px; } - &.sqllite:after { + &.sqlite:after { background: url('/assets/icons/sqllite.svg') bottom right no-repeat; + background-size: 50px; + } + + &__action { + position: absolute; + top: 2px; + right: 2px; } } diff --git a/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.ts b/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.ts index 7ebf19fd..cd97b608 100644 --- a/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.ts +++ b/apps/mix-cms/src/app/pages/portal/database/database-context/database-context.component.ts @@ -1,16 +1,31 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; -import { DatabaseProvider } from '@mixcore/lib/model'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { + DatabaseProvider, + DatabaseProviderDisplay, + MixDbContext, +} from '@mixcore/lib/model'; +import { MixApiFacadeService } from '@mixcore/share/api'; import { MixStatusIndicatorComponent, MixSubToolbarComponent, } from '@mixcore/share/components'; +import { toastObserverProcessing } from '@mixcore/share/helper'; import { RelativeTimeSpanPipe } from '@mixcore/share/pipe'; import { MixButtonComponent } from '@mixcore/ui/button'; +import { MixEmptyContentComponent } from '@mixcore/ui/empty-content'; import { DynamicFilterComponent } from '@mixcore/ui/filter'; +import { ModalService } from '@mixcore/ui/modal'; +import { SkeletonLoadingComponent } from '@mixcore/ui/skeleton'; import { MixDataTableModule } from '@mixcore/ui/table'; import { DialogService } from '@ngneat/dialog'; +import { TippyDirective } from '@ngneat/helipopper'; +import { HotToastService } from '@ngneat/hot-toast'; +import { tuiPure } from '@taiga-ui/cdk'; +import { TuiLoaderModule } from '@taiga-ui/core'; import { TuiCardModule, TuiSurfaceModule } from '@taiga-ui/experimental'; +import { TuiFilterModule } from '@taiga-ui/kit'; import { DbContextFormComponent } from '../components/db-context-form/db-context-form.component'; import { DatabaseContextStore } from '../store/db-context.store'; @@ -19,6 +34,7 @@ import { DatabaseContextStore } from '../store/db-context.store'; standalone: true, imports: [ CommonModule, + ReactiveFormsModule, MixSubToolbarComponent, MixButtonComponent, MixDataTableModule, @@ -27,6 +43,11 @@ import { DatabaseContextStore } from '../store/db-context.store'; DynamicFilterComponent, TuiCardModule, TuiSurfaceModule, + TuiFilterModule, + MixEmptyContentComponent, + SkeletonLoadingComponent, + TippyDirective, + TuiLoaderModule, ], templateUrl: './database-context.component.html', styleUrl: './database-context.component.scss', @@ -35,14 +56,31 @@ import { DatabaseContextStore } from '../store/db-context.store'; export class DatabaseContextComponent { public dialog = inject(DialogService); public store = inject(DatabaseContextStore); + public modal = inject(ModalService); + public mixApi = inject(MixApiFacadeService); + public toast = inject(HotToastService); public ProviderIconMap: Record = { [DatabaseProvider.SQLSERVER]: 'sqlserver', [DatabaseProvider.MySQL]: 'mysql', [DatabaseProvider.PostgreSQL]: 'postgres', - [DatabaseProvider.SQLLITE]: 'sqllite', + [DatabaseProvider.SQLITE]: 'sqlite', }; + public filterForm = inject(FormBuilder).control([]); + public filterItems = Object.values(DatabaseProvider); + public ProviderLabel = DatabaseProviderDisplay as any; + public selected?: MixDbContext; + + @tuiPure + public combineFilter( + value: MixDbContext[], + filterValue: DatabaseProvider[] | null + ): MixDbContext[] { + if (!filterValue?.length) return value; + return value.filter((v) => filterValue.includes(v.databaseProvider)); + } + public addContext() { const dialogRef = this.dialog.open(DbContextFormComponent, { windowClass: 'top-align-modal', @@ -52,4 +90,19 @@ export class DatabaseContextComponent { if (isReload) this.store.reload(); }); } + + public onDelete() { + if (!this.selected) return; + + this.modal.asKForAction('Are you sure to delete this item?', () => { + this.mixApi.databaseContext + .deleteById(this.selected!.id) + .pipe(toastObserverProcessing(this.toast)) + .subscribe({ + next: () => { + this.store.reload(); + }, + }); + }); + } } diff --git a/apps/mix-cms/src/app/pages/portal/database/store/db-context.store.ts b/apps/mix-cms/src/app/pages/portal/database/store/db-context.store.ts index bdfae874..0ecfa0bc 100644 --- a/apps/mix-cms/src/app/pages/portal/database/store/db-context.store.ts +++ b/apps/mix-cms/src/app/pages/portal/database/store/db-context.store.ts @@ -10,7 +10,7 @@ export class DatabaseContextStore extends BaseCRUDStore { this.mixApi.databaseContext.gets({ ...request, columns: '', pageSize: 50 }); public override vm$ = this.select((s) => s); - public override requestName = 'database-context'; + public override requestName = 'databaseContext'; public override searchColumns = ['Name', 'Description']; public override searchColumnsDict: { [key: string]: string } = { Name: 'displayName', diff --git a/apps/mix-cms/src/assets/icons/empty-content.svg b/apps/mix-cms/src/assets/icons/empty-content.svg new file mode 100644 index 00000000..fe8350ba --- /dev/null +++ b/apps/mix-cms/src/assets/icons/empty-content.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/mix-lib/src/model/core/database.model.ts b/libs/mix-lib/src/model/core/database.model.ts index 4e44fec4..8d1797ca 100644 --- a/libs/mix-lib/src/model/core/database.model.ts +++ b/libs/mix-lib/src/model/core/database.model.ts @@ -402,12 +402,16 @@ export enum DatabaseProvider { SQLSERVER = 'SQLSERVER', MySQL = 'MySQL', PostgreSQL = 'PostgreSQL', - SQLLITE = 'SQLLITE', + SQLITE = 'SQLITE', } -export const DatabaseProviderDisplay: Record = { +export const DatabaseProviderDisplay: Record< + DatabaseProvider | string, + string +> = { + All: 'All provider', [DatabaseProvider.SQLSERVER]: 'Microsoft Sql Server', [DatabaseProvider.MySQL]: 'My SQL', [DatabaseProvider.PostgreSQL]: 'Postgres Sql', - [DatabaseProvider.SQLLITE]: 'Sql Lite', + [DatabaseProvider.SQLITE]: 'Sql Lite', }; diff --git a/libs/mix-share/src/bases/base-crud.store.ts b/libs/mix-share/src/bases/base-crud.store.ts index 17c88b5f..4d919fef 100644 --- a/libs/mix-share/src/bases/base-crud.store.ts +++ b/libs/mix-share/src/bases/base-crud.store.ts @@ -109,7 +109,7 @@ export class BaseCRUDStore extends ComponentStore> { } public reload() { - this.patchState({ status: 'Loading' }); + this.patchState({ status: 'SilentLoading' }); this.loadData(this.request$()); } diff --git a/libs/mix-ui/src/empty-content/empty-content.component.html b/libs/mix-ui/src/empty-content/empty-content.component.html new file mode 100644 index 00000000..a06d8d39 --- /dev/null +++ b/libs/mix-ui/src/empty-content/empty-content.component.html @@ -0,0 +1,18 @@ + + survived + +

{{ title }}

+ + + {{ description }} + + + + Create + +
diff --git a/libs/mix-ui/src/empty-content/empty-content.component.scss b/libs/mix-ui/src/empty-content/empty-content.component.scss new file mode 100644 index 00000000..702fed44 --- /dev/null +++ b/libs/mix-ui/src/empty-content/empty-content.component.scss @@ -0,0 +1,6 @@ +.t-block-text { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} diff --git a/libs/mix-ui/src/empty-content/empty-content.component.ts b/libs/mix-ui/src/empty-content/empty-content.component.ts new file mode 100644 index 00000000..808f9f25 --- /dev/null +++ b/libs/mix-ui/src/empty-content/empty-content.component.ts @@ -0,0 +1,26 @@ +import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, + ViewEncapsulation, +} from '@angular/core'; +import { MixButtonComponent } from '@mixcore/ui/button'; +import { TuiBlockStatusModule } from '@taiga-ui/layout'; + +@Component({ + selector: 'mix-empty-content', + standalone: true, + imports: [CommonModule, TuiBlockStatusModule, MixButtonComponent], + templateUrl: './empty-content.component.html', + styleUrl: './empty-content.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, +}) +export class MixEmptyContentComponent { + @Input() title: string = 'No data found'; + @Input() description: string = 'Create your own wonderful data'; + @Output() actionClick = new EventEmitter(); +} diff --git a/libs/mix-ui/src/empty-content/index.ts b/libs/mix-ui/src/empty-content/index.ts new file mode 100644 index 00000000..60650c5d --- /dev/null +++ b/libs/mix-ui/src/empty-content/index.ts @@ -0,0 +1 @@ +export * from './empty-content.component'; diff --git a/libs/mix-ui/src/empty-content/ng-package.json b/libs/mix-ui/src/empty-content/ng-package.json new file mode 100644 index 00000000..1dc0b0bd --- /dev/null +++ b/libs/mix-ui/src/empty-content/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "index.ts" + } +} diff --git a/libs/mix-ui/src/empty-content/project.json b/libs/mix-ui/src/empty-content/project.json new file mode 100644 index 00000000..e5e0a6c6 --- /dev/null +++ b/libs/mix-ui/src/empty-content/project.json @@ -0,0 +1,18 @@ +{ + "name": "@mixcore/ui/empty-content", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "sourceRoot": "libs/mix-ui/src/error-alert", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "libs/mix-ui/src/empty-content/**/*.ts", + "libs/mix-ui/src/empty-content/**/*.html" + ] + } + } + } +} diff --git a/package.json b/package.json index 6834eb18..1f191dc9 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@taiga-ui/experimental": "^3.57.0", "@taiga-ui/icons": "3.55.0", "@taiga-ui/kit": "3.55.0", + "@taiga-ui/layout": "^3.57.0", "@tinkoff/ng-dompurify": "^4.0.0", "@tinkoff/ng-polymorpheus": "4.2.0", "ag-grid-angular": "^30.0.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5dc517b3..8fcb4ddd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,6 +113,9 @@ dependencies: '@taiga-ui/kit': specifier: 3.55.0 version: 3.55.0(@angular/common@17.0.2)(@angular/core@17.0.2)(@angular/forms@17.0.2)(@angular/router@17.0.2)(@ng-web-apis/common@3.0.6)(@ng-web-apis/mutation-observer@3.0.6)(@ng-web-apis/resize-observer@3.0.6)(@taiga-ui/cdk@3.55.0)(@taiga-ui/core@3.55.0)(@taiga-ui/i18n@3.55.0)(@tinkoff/ng-polymorpheus@4.2.0)(rxjs@7.8.1) + '@taiga-ui/layout': + specifier: ^3.57.0 + version: 3.57.0(@angular/common@17.0.2)(@angular/core@17.0.2)(@taiga-ui/cdk@3.55.0)(@taiga-ui/core@3.55.0)(@tinkoff/ng-polymorpheus@4.2.0)(rxjs@7.8.1) '@tinkoff/ng-dompurify': specifier: ^4.0.0 version: 4.0.0(@angular/core@17.0.2)(@angular/platform-browser@17.0.2)(@types/dompurify@3.0.5)(dompurify@3.0.6) @@ -5411,6 +5414,25 @@ packages: tslib: 2.6.2 dev: false + /@taiga-ui/layout@3.57.0(@angular/common@17.0.2)(@angular/core@17.0.2)(@taiga-ui/cdk@3.55.0)(@taiga-ui/core@3.55.0)(@tinkoff/ng-polymorpheus@4.2.0)(rxjs@7.8.1): + resolution: {integrity: sha512-BG5mK7cb6e3GOOq67bGiyg/2ppsfn0576jlcSTJWQ6zWxl4KaL5ya0GWeSSR5zuGZEUHH01wq16wJqJy+bEuLw==} + peerDependencies: + '@angular/common': '>=12.0.0' + '@angular/core': '>=12.0.0' + '@taiga-ui/cdk': '>=3.57.0' + '@taiga-ui/core': '>=3.57.0' + '@tinkoff/ng-polymorpheus': 4.3.0 + rxjs: '>=6.0.0' + dependencies: + '@angular/common': 17.0.2(@angular/core@17.0.2)(rxjs@7.8.1) + '@angular/core': 17.0.2(rxjs@7.8.1)(zone.js@0.14.2) + '@taiga-ui/cdk': 3.55.0(@angular-devkit/core@17.0.0)(@angular-devkit/schematics@17.0.0)(@angular/animations@17.0.2)(@angular/common@17.0.2)(@angular/core@17.0.2)(@angular/forms@17.0.2)(@angular/platform-browser@17.0.2)(rxjs@7.8.1) + '@taiga-ui/core': 3.55.0(@angular/animations@17.0.2)(@angular/common@17.0.2)(@angular/core@17.0.2)(@angular/forms@17.0.2)(@angular/platform-browser@17.0.2)(@angular/router@17.0.2)(@ng-web-apis/common@3.0.6)(@ng-web-apis/mutation-observer@3.0.6)(@taiga-ui/cdk@3.55.0)(@taiga-ui/i18n@3.55.0)(@tinkoff/ng-event-plugins@3.1.0)(@tinkoff/ng-polymorpheus@4.2.0)(rxjs@7.8.1) + '@tinkoff/ng-polymorpheus': 4.2.0(@angular/core@17.0.2)(@angular/platform-browser@17.0.2) + rxjs: 7.8.1 + tslib: 2.6.2 + dev: false + /@tinkoff/ng-dompurify@4.0.0(@angular/core@17.0.2)(@angular/platform-browser@17.0.2)(@types/dompurify@3.0.5)(dompurify@3.0.6): resolution: {integrity: sha512-BjKUweWLrOx8UOZw+Tl+Dae5keYuSbeMkppcXQdsvwASMrPfmP7d3Q206Q6HDqOV2WnpnFqGUB95IMbLAeRRuw==} peerDependencies: diff --git a/tsconfig.base.json b/tsconfig.base.json index 930192a4..db270124 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -62,7 +62,8 @@ "@mixcore/ui/table": ["libs/mix-ui/src/data-table/index.ts"], "@mixcore/ui/textarea": ["libs/mix-ui/src/text-area/index.ts"], "@mixcore/ui/toggle": ["libs/mix-ui/src/toggle/index.ts"], - "@mixcore/ui/upload": ["libs/mix-ui/src/upload/index.ts"] + "@mixcore/ui/upload": ["libs/mix-ui/src/upload/index.ts"], + "@mixcore/ui/empty-content": ["libs/mix-ui/src/empty-content/index.ts"] } }, "exclude": ["node_modules", "tmp"]