From 24d5e18ffa0eddc857c8d2bb291b7a7cbcdd4f5a Mon Sep 17 00:00:00 2001 From: amirbarari Date: Sun, 31 Aug 2025 13:37:14 +0330 Subject: [PATCH 001/100] create and setup home page in its child components --- etl_frontend/src/app/app.routes.ts | 2 +- .../components/home/home.component.html | 2 ++ .../components/home/home.component.scss | 0 .../components/home/home.component.spec.ts | 23 +++++++++++++++++++ .../components/home/home.component.ts | 11 +++++++++ .../components/home/home.module.routing.ts | 20 ++++++++++++++++ .../dashboard/components/home/home.module.ts | 20 ++++++++++++++++ .../manage-files/manage-files.component.html | 1 + .../manage-files/manage-files.component.scss | 0 .../manage-files.component.spec.ts | 23 +++++++++++++++++++ .../manage-files/manage-files.component.ts | 11 +++++++++ .../manage-workflows.component.html | 1 + .../manage-workflows.component.scss | 0 .../manage-workflows.component.spec.ts | 23 +++++++++++++++++++ .../manage-workflows.component.ts | 11 +++++++++ .../dashboard/dashboard.module.routing.ts | 9 +++++++- 16 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 etl_frontend/src/app/features/dashboard/components/home/home.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/home/home.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/home/home.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/home.component.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/home.module.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.ts diff --git a/etl_frontend/src/app/app.routes.ts b/etl_frontend/src/app/app.routes.ts index d8f9ed9e..df314bbc 100644 --- a/etl_frontend/src/app/app.routes.ts +++ b/etl_frontend/src/app/app.routes.ts @@ -12,7 +12,7 @@ export const routes: Routes = [ path: 'dashboard', loadChildren: () => import('./features/dashboard/dashboard.module').then(m => m.DashboardModule), canMatch: [], //TODO check this - data: { role: "data_admin" } + // data: { role: "data_admin" } }, { path: 'landing', diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.component.html b/etl_frontend/src/app/features/dashboard/components/home/home.component.html new file mode 100644 index 00000000..71eb9765 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/home.component.html @@ -0,0 +1,2 @@ +

home works!

+ \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.component.scss b/etl_frontend/src/app/features/dashboard/components/home/home.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/home.component.spec.ts new file mode 100644 index 00000000..1191557f --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/home.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomeComponent } from './home.component'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HomeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.component.ts b/etl_frontend/src/app/features/dashboard/components/home/home.component.ts new file mode 100644 index 00000000..7b8046ab --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/home.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-home', + standalone: false, + templateUrl: './home.component.html', + styleUrl: './home.component.scss' +}) +export class HomeComponent { + +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts b/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts new file mode 100644 index 00000000..fb0228c1 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts @@ -0,0 +1,20 @@ +import { Routes } from "@angular/router"; +import { HomeComponent } from "./home.component"; + + +export const homeRoutes: Routes = [ + { + path: '', + component: HomeComponent, + children: [ + { + path: 'workflows', + loadComponent: () => import('./manage-workflows/manage-workflows.component').then(m => m.ManageWorkflowsComponent) + }, + { + path: 'files', + loadComponent: () => import('./manage-files/manage-files.component').then(m => m.ManageFilesComponent) + } + ] + }, +] \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.module.ts b/etl_frontend/src/app/features/dashboard/components/home/home.module.ts new file mode 100644 index 00000000..07254148 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/home.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { HomeComponent } from './home.component'; +import { RouterModule } from '@angular/router'; +import { homeRoutes } from './home.module.routing'; +import { ManageFilesComponent } from './manage-files/manage-files.component'; +import { manageUserRoutes } from '../../../profile/components/manage-user/manage-user.module.routing'; +import { ManageWorkflowsComponent } from './manage-workflows/manage-workflows.component'; + + + +@NgModule({ + declarations: [HomeComponent, ManageFilesComponent, ManageWorkflowsComponent], + imports: [ + CommonModule, + RouterModule.forChild(homeRoutes), + RouterModule, + ] +}) +export class HomeModule { } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html new file mode 100644 index 00000000..78469913 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html @@ -0,0 +1 @@ +

manage-files works!

diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.spec.ts new file mode 100644 index 00000000..efa88998 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ManageFilesComponent } from './manage-files.component'; + +describe('ManageFilesComponent', () => { + let component: ManageFilesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ManageFilesComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ManageFilesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.ts new file mode 100644 index 00000000..620e9d7c --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-manage-files', + standalone: false, + templateUrl: './manage-files.component.html', + styleUrl: './manage-files.component.scss' +}) +export class ManageFilesComponent { + +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.html new file mode 100644 index 00000000..1d895b28 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.html @@ -0,0 +1 @@ +

manage-workflows works!

diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.spec.ts new file mode 100644 index 00000000..bfa2ded0 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ManageWorkflowsComponent } from './manage-workflows.component'; + +describe('ManageWorkflowsComponent', () => { + let component: ManageWorkflowsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ManageWorkflowsComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ManageWorkflowsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.ts new file mode 100644 index 00000000..e0fb0416 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-workflows/manage-workflows.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-manage-workflows', + standalone: false, + templateUrl: './manage-workflows.component.html', + styleUrl: './manage-workflows.component.scss' +}) +export class ManageWorkflowsComponent { + +} diff --git a/etl_frontend/src/app/features/dashboard/dashboard.module.routing.ts b/etl_frontend/src/app/features/dashboard/dashboard.module.routing.ts index a0375e04..7ec2e85a 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.module.routing.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.module.routing.ts @@ -4,6 +4,13 @@ import { DashboardComponent } from "./dashboard.component"; export const dashboardRoutes: Routes = [ { path: '', - component: DashboardComponent + component: DashboardComponent, + children: [ + { + path: 'home', + loadChildren: () => import('./components/home/home.module').then(m => m.HomeModule) + }, + // main layout phase 3 + ] }, ] From b98de633f6f81a363a8dd14ad2fd42c4ebcbe2a2 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Sun, 31 Aug 2025 14:30:06 +0330 Subject: [PATCH 002/100] fix : home and nested components routing --- etl_frontend/src/app/app.routes.ts | 4 ++-- .../components/home/home.module.routing.ts | 13 +++++++++++-- .../app/features/dashboard/dashboard.component.html | 3 ++- .../error-handling/error-handling.interceptor.ts | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/etl_frontend/src/app/app.routes.ts b/etl_frontend/src/app/app.routes.ts index df314bbc..52f703ed 100644 --- a/etl_frontend/src/app/app.routes.ts +++ b/etl_frontend/src/app/app.routes.ts @@ -5,8 +5,8 @@ export const routes: Routes = [ { path: '', pathMatch: 'full', - // redirectTo: 'dashboard' - redirectTo: 'landing' + redirectTo: 'dashboard' + // redirectTo: 'landing' }, { path: 'dashboard', diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts b/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts index fb0228c1..ddf5f04d 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts @@ -1,5 +1,7 @@ import { Routes } from "@angular/router"; import { HomeComponent } from "./home.component"; +import { ManageWorkflowsComponent } from "./manage-workflows/manage-workflows.component"; +import { ManageFilesComponent } from "./manage-files/manage-files.component"; export const homeRoutes: Routes = [ @@ -7,13 +9,20 @@ export const homeRoutes: Routes = [ path: '', component: HomeComponent, children: [ + { + path: '', + pathMatch: 'full', + redirectTo: 'workflows' + }, { path: 'workflows', - loadComponent: () => import('./manage-workflows/manage-workflows.component').then(m => m.ManageWorkflowsComponent) + component: ManageWorkflowsComponent + // loadComponent: () => import('./manage-workflows/manage-workflows.component').then(m => m.ManageWorkflowsComponent) }, { path: 'files', - loadComponent: () => import('./manage-files/manage-files.component').then(m => m.ManageFilesComponent) + component: ManageFilesComponent, + // loadComponent: () => import('./manage-files/manage-files.component').then(m => m.ManageFilesComponent) } ] }, diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.html b/etl_frontend/src/app/features/dashboard/dashboard.component.html index 9c231143..14d8a666 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.html +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.html @@ -1,7 +1,7 @@
logo image - +
@if (user(); as currentUser) { @@ -9,3 +9,4 @@ }
+ \ No newline at end of file diff --git a/etl_frontend/src/app/shared/interceptors/error-handling/error-handling.interceptor.ts b/etl_frontend/src/app/shared/interceptors/error-handling/error-handling.interceptor.ts index 701e5380..86765283 100644 --- a/etl_frontend/src/app/shared/interceptors/error-handling/error-handling.interceptor.ts +++ b/etl_frontend/src/app/shared/interceptors/error-handling/error-handling.interceptor.ts @@ -13,7 +13,7 @@ export const errorHandlingInterceptor: HttpInterceptorFn = (req, next) => { return next(req).pipe( catchError((error: HttpErrorResponse) => { if (error.status === 401) { - router.navigate(['/landing']); + // router.navigate(['/landing']); userStore.clearUser(); messageService.add({ severity: 'warn', From 4897728c253eae240bd0bf2a3a48719643729a6f Mon Sep 17 00:00:00 2001 From: Mohammad Amin Dadkhah Date: Sun, 31 Aug 2025 22:11:57 +0330 Subject: [PATCH 003/100] feat: add design for sidebar --- .../dashboard/dashboard.component.html | 5 +++++ .../dashboard/dashboard.component.scss | 8 ++++++++ .../features/dashboard/dashboard.component.ts | 20 +++++++++++++++++++ .../features/dashboard/dashboard.module.ts | 2 ++ etl_frontend/src/styles.scss | 10 ++++++++++ 5 files changed, 45 insertions(+) diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.html b/etl_frontend/src/app/features/dashboard/dashboard.component.html index 9c231143..39de5506 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.html +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.html @@ -9,3 +9,8 @@ } + +
+ +
+ diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.scss b/etl_frontend/src/app/features/dashboard/dashboard.component.scss index 49dca8c2..fdc44496 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.scss +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.scss @@ -12,3 +12,11 @@ } } } + +.dashboard { + &__sidebar { + inline-size: 20vw; + block-size: 100vh; + } +} + diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.ts b/etl_frontend/src/app/features/dashboard/dashboard.component.ts index f1e26b55..b773e791 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.ts @@ -1,5 +1,7 @@ import {Component, computed, OnInit} from '@angular/core'; import {UserStoreService} from "../../shared/stores/user-store.service" +import { MenuItem } from 'primeng/api'; + @Component({ selector: 'app-dashboard', @@ -11,6 +13,24 @@ export class DashboardComponent implements OnInit { public readonly user = computed(() => this.userStore.vm().user); public readonly isLoading = computed(() => this.userStore.vm().isLoading); + public readonly menuItems: MenuItem[] = [ + { + label: 'DataWave', + items: [ + { + label: 'Workflow History', + icon: 'pi pi-file-plus', + routerLink: '/profile', + }, + { + label: 'Data Management', + icon: 'pi pi-file-import', + routerLink: '/profile', + } + ] + } + ]; + constructor(private userStore: UserStoreService) {} ngOnInit() { diff --git a/etl_frontend/src/app/features/dashboard/dashboard.module.ts b/etl_frontend/src/app/features/dashboard/dashboard.module.ts index d9ff16ff..2b5ed717 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.module.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.module.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { Button } from 'primeng/button'; import { Toolbar } from 'primeng/toolbar'; import { Avatar } from 'primeng/avatar'; +import { Menu } from 'primeng/menu'; import { DashboardComponent } from './dashboard.component'; import { RouterModule } from '@angular/router'; import { dashboardRoutes } from './dashboard.module.routing'; @@ -19,6 +20,7 @@ import {ProfilePopoverComponent} from './components/profile-popover/profile-popo RouterModule.forChild(dashboardRoutes), HeaderComponent, ProfilePopoverComponent, + Menu, ], }) export class DashboardModule { } diff --git a/etl_frontend/src/styles.scss b/etl_frontend/src/styles.scss index ea2014b1..f257f81f 100644 --- a/etl_frontend/src/styles.scss +++ b/etl_frontend/src/styles.scss @@ -37,3 +37,13 @@ p-toolbar .p-toolbar { transform: scale(1) translateX(-10%); } } + +p-menu .p-menu { + padding-inline-start: 0.8rem; + border-block: none; + border-inline-start: none; +} + +.p-menu-list { + block-size: 90.6vh; +} From 15718f2fb6f645fe943d72ad7acf1229d9d9654b Mon Sep 17 00:00:00 2001 From: amirbarari Date: Sun, 31 Aug 2025 23:21:51 +0330 Subject: [PATCH 004/100] creating needed components, store and services for managing workflows tabs --- .../workflow-tab/workflow-tab.component.html | 1 + .../workflow-tab/workflow-tab.component.scss | 0 .../workflow-tab.component.spec.ts | 23 +++++++++++++++++++ .../workflow-tab/workflow-tab.component.ts | 11 +++++++++ .../workflows-tabs-management.component.html | 1 + .../workflows-tabs-management.component.scss | 0 ...orkflows-tabs-management.component.spec.ts | 23 +++++++++++++++++++ .../workflows-tabs-management.component.ts | 11 +++++++++ .../workflows-list-management.service.spec.ts | 16 +++++++++++++ .../workflows-list-management.service.ts | 9 ++++++++ .../workflows-list-store.service.spec.ts | 16 +++++++++++++ .../workflows-list-store.service.ts | 9 ++++++++ 12 files changed, 120 insertions(+) create mode 100644 etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts create mode 100644 etl_frontend/src/app/features/dashboard/services/workflows-list-management/workflows-list-management.service.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/services/workflows-list-management/workflows-list-management.service.ts create mode 100644 etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html new file mode 100644 index 00000000..ab895b87 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html @@ -0,0 +1 @@ +

workflow-tab works!

diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.spec.ts new file mode 100644 index 00000000..2c7d0d09 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkflowTabComponent } from './workflow-tab.component'; + +describe('WorkflowTabComponent', () => { + let component: WorkflowTabComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WorkflowTabComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(WorkflowTabComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts new file mode 100644 index 00000000..b84d9327 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-workflow-tab', + imports: [], + templateUrl: './workflow-tab.component.html', + styleUrl: './workflow-tab.component.scss' +}) +export class WorkflowTabComponent { + +} diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html new file mode 100644 index 00000000..097e003d --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html @@ -0,0 +1 @@ +

workflows-tabs-management works!

diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.spec.ts new file mode 100644 index 00000000..6b1e7056 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkflowsTabsManagementComponent } from './workflows-tabs-management.component'; + +describe('WorkflowsTabsManagementComponent', () => { + let component: WorkflowsTabsManagementComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WorkflowsTabsManagementComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(WorkflowsTabsManagementComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts new file mode 100644 index 00000000..a3b32959 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-workflows-tabs-management', + imports: [], + templateUrl: './workflows-tabs-management.component.html', + styleUrl: './workflows-tabs-management.component.scss' +}) +export class WorkflowsTabsManagementComponent { + +} diff --git a/etl_frontend/src/app/features/dashboard/services/workflows-list-management/workflows-list-management.service.spec.ts b/etl_frontend/src/app/features/dashboard/services/workflows-list-management/workflows-list-management.service.spec.ts new file mode 100644 index 00000000..fa3feee3 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/services/workflows-list-management/workflows-list-management.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { WorkflowsListManagementService } from './workflows-list-management.service'; + +describe('WorkflowsListManagementService', () => { + let service: WorkflowsListManagementService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(WorkflowsListManagementService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/services/workflows-list-management/workflows-list-management.service.ts b/etl_frontend/src/app/features/dashboard/services/workflows-list-management/workflows-list-management.service.ts new file mode 100644 index 00000000..7b059a37 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/services/workflows-list-management/workflows-list-management.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class WorkflowsListManagementService { + + constructor() { } +} diff --git a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.spec.ts b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.spec.ts new file mode 100644 index 00000000..0b36cee5 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { WorkflowsListStore } from './workflows-list-store.service'; + +describe('WorkflowsListStore', () => { + let service: WorkflowsListStore; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(WorkflowsListStore); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts new file mode 100644 index 00000000..3dde833a --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class WorkflowsListStore { + + constructor() { } +} \ No newline at end of file From 50b104e6bd3faffb8ecd251ae8a8d816c9eb8140 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Mon, 1 Sep 2025 08:53:44 +0330 Subject: [PATCH 005/100] handle showing list of workflows tabs --- .../workflow-tab/workflow-tab.component.html | 2 +- .../workflow-tab/workflow-tab.component.ts | 2 +- .../workflows-tabs-management.component.html | 11 ++- .../workflows-tabs-management.component.scss | 9 +++ .../workflows-tabs-management.component.ts | 10 ++- .../dashboard/dashboard.component.html | 3 +- .../features/dashboard/dashboard.module.ts | 6 +- .../dashboard/models/workflow.model.ts | 16 ++++ .../workflows-list-store.service.ts | 78 +++++++++++++++++-- 9 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 etl_frontend/src/app/features/dashboard/models/workflow.model.ts diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html index ab895b87..b20e2155 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html @@ -1 +1 @@ -

workflow-tab works!

+

tab

\ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts index b84d9327..ad03e3c2 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts @@ -2,7 +2,7 @@ import { Component } from '@angular/core'; @Component({ selector: 'app-workflow-tab', - imports: [], + standalone: false, templateUrl: './workflow-tab.component.html', styleUrl: './workflow-tab.component.scss' }) diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html index 097e003d..73bbd229 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html @@ -1 +1,10 @@ -

workflows-tabs-management works!

+
+
+ @for (tab of openWorkflows$ | async; track $index) { + + } +
+
+ +
+
diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss index e69de29b..f1330415 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss @@ -0,0 +1,9 @@ +.tabs-bar { + display: flex; + align-items: center; + gap: .5rem; + &__tabs { + display: flex; + gap: 1rem; + } +} \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts index a3b32959..7ac1c435 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts @@ -1,11 +1,17 @@ import { Component } from '@angular/core'; +import { WorkflowsListStore } from '../../stores/workflows-list/workflows-list-store.service'; @Component({ selector: 'app-workflows-tabs-management', - imports: [], + standalone: false, templateUrl: './workflows-tabs-management.component.html', styleUrl: './workflows-tabs-management.component.scss' }) export class WorkflowsTabsManagementComponent { - + public readonly vm; + public readonly openWorkflows$; + constructor(public readonly workflowListStore: WorkflowsListStore) { + this.vm = this.workflowListStore.vm; + this.openWorkflows$ = this.workflowListStore.getOpendWorkflows() + } } diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.html b/etl_frontend/src/app/features/dashboard/dashboard.component.html index 14d8a666..295f888e 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.html +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.html @@ -1,7 +1,8 @@
logo image - + +
@if (user(); as currentUser) { diff --git a/etl_frontend/src/app/features/dashboard/dashboard.module.ts b/etl_frontend/src/app/features/dashboard/dashboard.module.ts index d9ff16ff..7027c55c 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.module.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.module.ts @@ -8,9 +8,12 @@ import { RouterModule } from '@angular/router'; import { dashboardRoutes } from './dashboard.module.routing'; import {HeaderComponent} from "../../shared/components/header/header.component"; import {ProfilePopoverComponent} from './components/profile-popover/profile-popover.component'; +import { WorkflowsListStore } from './stores/workflows-list/workflows-list-store.service'; +import { WorkflowsTabsManagementComponent } from './components/workflows-tabs-management/workflows-tabs-management.component'; +import { WorkflowTabComponent } from './components/workflows-tabs-management/components/workflow-tab/workflow-tab.component'; @NgModule({ - declarations: [DashboardComponent], + declarations: [DashboardComponent, WorkflowsTabsManagementComponent, WorkflowTabComponent], imports: [ CommonModule, Button, @@ -20,5 +23,6 @@ import {ProfilePopoverComponent} from './components/profile-popover/profile-popo HeaderComponent, ProfilePopoverComponent, ], + providers: [WorkflowsListStore] }) export class DashboardModule { } diff --git a/etl_frontend/src/app/features/dashboard/models/workflow.model.ts b/etl_frontend/src/app/features/dashboard/models/workflow.model.ts new file mode 100644 index 00000000..53151ce7 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/models/workflow.model.ts @@ -0,0 +1,16 @@ +export interface WorkflowInfo { + id: string; + name: string; + description?: string; + createdAt?: Date; + updatedAt?: Date; + status?: 'draft' | 'running' | 'completed' | 'failed'; +} + +export interface WorkflowsListState { + workflows: WorkflowInfo[]; + openedWorkflowsId: string[]; + selectedWorkflowId: string | null; + isLoading: boolean; + error: string | null; +} diff --git a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts index 3dde833a..10ac56be 100644 --- a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts +++ b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts @@ -1,9 +1,77 @@ import { Injectable } from '@angular/core'; +import { ComponentStore } from '@ngrx/component-store'; +import { WorkflowInfo, WorkflowsListState } from '../../models/workflow.model'; +import { delay, Observable, of, tap } from 'rxjs'; -@Injectable({ - providedIn: 'root' -}) -export class WorkflowsListStore { +const initialState: WorkflowsListState = { + workflows: [{ + id: 'wf_001', + name: 'Customer Segmentation', + description: 'Cluster customers based on purchase history.', + createdAt: new Date('2025-07-15T10:30:00Z'), + updatedAt: new Date('2025-08-20T14:10:00Z'), + status: 'draft' + }, + { + id: 'wf_002', + name: 'Sales Data Cleaning', + description: 'Remove duplicates and fix missing values in sales dataset.', + createdAt: new Date('2025-08-01T09:00:00Z'), + updatedAt: new Date('2025-08-25T16:45:00Z'), + status: 'completed' + }, + { + id: 'wf_003', + name: 'Churn Prediction', + description: 'Predict customer churn using logistic regression.', + createdAt: new Date('2025-08-10T11:15:00Z'), + updatedAt: new Date('2025-08-29T18:20:00Z'), + status: 'running' + }, + { + id: 'wf_004', + name: 'Marketing Campaign Analysis', + description: 'Analyze effectiveness of email campaigns.', + createdAt: new Date('2025-07-28T13:00:00Z'), + updatedAt: new Date('2025-08-27T09:50:00Z'), + status: 'failed' + }, + { + id: 'wf_005', + name: 'Inventory Forecasting', + description: 'Time-series forecasting of inventory demand.', + createdAt: new Date('2025-08-05T15:45:00Z'), + updatedAt: new Date('2025-08-30T12:00:00Z'), + status: 'draft' + }], + error: null, + isLoading: false, + openedWorkflowsId: ['wf_005', 'wf_004', 'wf_001'], + selectedWorkflowId: '' +} - constructor() { } +@Injectable() +export class WorkflowsListStore extends ComponentStore { + + constructor() { + super(initialState); + } + + public readonly vm = this.selectSignal(s => s); + + // public loadWorkflows = this.effect(trigger$ => { //TODO call api in service to fetch list of workflows + // return trigger$ + // }) + + public getWorkflows(): Observable { + return of(this.vm().workflows).pipe( + tap(() => this.patchState({ isLoading: true })), + delay(3000), + tap(() => this.patchState({ isLoading: false })) + ) + } + + public getOpendWorkflows(): Observable { + return of(this.vm().workflows.filter((wf) => this.vm().openedWorkflowsId.includes(wf.id))); + } } \ No newline at end of file From d415c28f0ede1fe430d7ec461e18a0cde2218324 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Mon, 1 Sep 2025 10:18:20 +0330 Subject: [PATCH 006/100] implementing workflow tab component --- .../workflow-tab/workflow-tab.component.html | 11 ++- .../workflow-tab/workflow-tab.component.scss | 74 +++++++++++++++++++ .../workflow-tab/workflow-tab.component.ts | 5 +- .../workflows-tabs-management.component.html | 2 +- .../features/dashboard/dashboard.module.ts | 2 + 5 files changed, 90 insertions(+), 4 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html index b20e2155..d1ac579d 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html @@ -1 +1,10 @@ -

tab

\ No newline at end of file +
+
+
{{ workflowInfo().name }}
+
+ +
+
diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss index e69de29b..75f67397 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss @@ -0,0 +1,74 @@ +.tab { + display: flex; + align-items: center; + justify-content: space-evenly; + cursor: pointer; + border-radius: 1rem; + padding-inline: 0.5rem; + transition: background 0.2s linear; + inline-size: 10rem; + max-inline-size: 10rem; + height: 2.5rem; + background-color: var(--p-surface-100); // light gray + + &:hover { + background-color: var(--p-primary-200); + } + &:hover &__close-icon { + display: block; + } + + &__name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + // transition: max-width 0.3s ease; + } + + &__status { + inline-size: 0.55rem; + aspect-ratio: 1; + border-radius: 50%; + margin-right: 0.5rem; + + &-draft { + background: gray; + } + &-running { + background: blue; + } + &-completed { + background: green; + } + &-failed { + background: red; + animation: blinking 0.8s cubic-bezier(0.39, 0.575, 0.565, 1) infinite alternate-reverse; + } + } + + &__close-icon { + display: none; + animation: scale-up 0.2s linear; + } +} + +@keyframes scale-up { + from { + transform: scale(0.3); + } + to { + transform: scale(1); + } +} + +@keyframes blinking { + from { + transform: scale(0.5); + opacity: 0.3; + } + to { + transform: scale(1); + opacity: 1; + } +} diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts index ad03e3c2..6d627778 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts @@ -1,4 +1,5 @@ -import { Component } from '@angular/core'; +import { Component, input } from '@angular/core'; +import { WorkflowInfo } from '../../../../models/workflow.model'; @Component({ selector: 'app-workflow-tab', @@ -7,5 +8,5 @@ import { Component } from '@angular/core'; styleUrl: './workflow-tab.component.scss' }) export class WorkflowTabComponent { - + workflowInfo = input.required(); } diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html index 73bbd229..9cf6a385 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html @@ -1,7 +1,7 @@
@for (tab of openWorkflows$ | async; track $index) { - + }
diff --git a/etl_frontend/src/app/features/dashboard/dashboard.module.ts b/etl_frontend/src/app/features/dashboard/dashboard.module.ts index 7027c55c..b344dad2 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.module.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.module.ts @@ -11,6 +11,7 @@ import {ProfilePopoverComponent} from './components/profile-popover/profile-popo import { WorkflowsListStore } from './stores/workflows-list/workflows-list-store.service'; import { WorkflowsTabsManagementComponent } from './components/workflows-tabs-management/workflows-tabs-management.component'; import { WorkflowTabComponent } from './components/workflows-tabs-management/components/workflow-tab/workflow-tab.component'; +import { TooltipModule } from 'primeng/tooltip'; @NgModule({ declarations: [DashboardComponent, WorkflowsTabsManagementComponent, WorkflowTabComponent], @@ -22,6 +23,7 @@ import { WorkflowTabComponent } from './components/workflows-tabs-management/com RouterModule.forChild(dashboardRoutes), HeaderComponent, ProfilePopoverComponent, + TooltipModule ], providers: [WorkflowsListStore] }) From 64fd06075b96e10c2aea4ad10e4031112b4181a7 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Mon, 1 Sep 2025 14:42:08 +0330 Subject: [PATCH 007/100] handle creating new workflow --- .../workflows-tabs-management.component.html | 4 +- .../workflows-tabs-management.component.ts | 10 ++- .../dashboard/models/workflow.model.ts | 3 +- .../workflows-list-store.service.ts | 70 +++++++++++++++---- 4 files changed, 67 insertions(+), 20 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html index 9cf6a385..6d35a44c 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html @@ -1,10 +1,10 @@
- @for (tab of openWorkflows$ | async; track $index) { + @for (tab of vm().openWorkflows; track tab.id) { }
- +
diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts index 7ac1c435..1596b708 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { WorkflowsListStore } from '../../stores/workflows-list/workflows-list-store.service'; +import { of } from 'rxjs'; @Component({ selector: 'app-workflows-tabs-management', @@ -9,9 +10,14 @@ import { WorkflowsListStore } from '../../stores/workflows-list/workflows-list-s }) export class WorkflowsTabsManagementComponent { public readonly vm; - public readonly openWorkflows$; + // public readonly openWorkflows$; + constructor(public readonly workflowListStore: WorkflowsListStore) { this.vm = this.workflowListStore.vm; - this.openWorkflows$ = this.workflowListStore.getOpendWorkflows() + // this.openWorkflows$ = this.workflowListStore.getOpendWorkflows() + } + + onCreateNewWorkflow():void { + this.workflowListStore.createNewWorkflow(of({workflowName: "New Workflow"})); } } diff --git a/etl_frontend/src/app/features/dashboard/models/workflow.model.ts b/etl_frontend/src/app/features/dashboard/models/workflow.model.ts index 53151ce7..60bfd30c 100644 --- a/etl_frontend/src/app/features/dashboard/models/workflow.model.ts +++ b/etl_frontend/src/app/features/dashboard/models/workflow.model.ts @@ -11,6 +11,7 @@ export interface WorkflowsListState { workflows: WorkflowInfo[]; openedWorkflowsId: string[]; selectedWorkflowId: string | null; - isLoading: boolean; + isLoadingWorkflows: boolean; error: string | null; + isCreatingWorkflow: boolean; } diff --git a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts index 10ac56be..b94039d1 100644 --- a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts +++ b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts @@ -1,7 +1,7 @@ -import { Injectable } from '@angular/core'; +import { effect, Injectable } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { WorkflowInfo, WorkflowsListState } from '../../models/workflow.model'; -import { delay, Observable, of, tap } from 'rxjs'; +import { catchError, delay, exhaustMap, Observable, of, tap } from 'rxjs'; const initialState: WorkflowsListState = { workflows: [{ @@ -45,9 +45,10 @@ const initialState: WorkflowsListState = { status: 'draft' }], error: null, - isLoading: false, + isLoadingWorkflows: false, openedWorkflowsId: ['wf_005', 'wf_004', 'wf_001'], - selectedWorkflowId: '' + selectedWorkflowId: '', + isCreatingWorkflow: false, } @Injectable() @@ -57,21 +58,60 @@ export class WorkflowsListStore extends ComponentStore { super(initialState); } - public readonly vm = this.selectSignal(s => s); + public readonly vm = this.selectSignal(s => { + return { + ...s, + openWorkflows: s.workflows.filter((wf) => + s.openedWorkflowsId.includes(wf.id) + ) + } + } + ); // public loadWorkflows = this.effect(trigger$ => { //TODO call api in service to fetch list of workflows // return trigger$ // }) - public getWorkflows(): Observable { - return of(this.vm().workflows).pipe( - tap(() => this.patchState({ isLoading: true })), - delay(3000), - tap(() => this.patchState({ isLoading: false })) - ) - } + // public getWorkflows(): Observable { + // return of(this.vm().workflows).pipe( + // tap(() => this.patchState({ isLoadingWorkflows: true })), + // delay(3000), + // tap(() => this.patchState({ isLoadingWorkflows: false })) + // ) + // } + + // public getOpendWorkflows(): Observable { + // return of(this.vm().workflows.filter((wf) => this.vm().openedWorkflowsId.includes(wf.id))); + // } + + public createNewWorkflow = this.effect<{ workflowName: string }>(workflow$ => { + return workflow$.pipe( + tap(() => this.patchState({ isCreatingWorkflow: true })), + exhaustMap(({ workflowName }) => { + // TODO: replace with actual service call + return of({ + id: 'wf_' + Math.floor(Math.random() * 10000), // generate unique id + name: workflowName, + description: 'New workflow created by user.', + createdAt: new Date(), + updatedAt: new Date(), + status: 'running' + } as WorkflowInfo).pipe( + tap((newWorkflow) => { + this.patchState({ + workflows: [...this.vm().workflows, newWorkflow], + openedWorkflowsId: [...this.vm().openedWorkflowsId, newWorkflow.id], + selectedWorkflowId: newWorkflow.id, + isCreatingWorkflow: false + }); + }), + catchError((error) => { + this.patchState({ isCreatingWorkflow: false, error }); + return of(error); + }) + ); + }) + ); + }); - public getOpendWorkflows(): Observable { - return of(this.vm().workflows.filter((wf) => this.vm().openedWorkflowsId.includes(wf.id))); - } } \ No newline at end of file From 4a3bf7281da55fcc540a8d8c8162741adde7fcd8 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Mon, 1 Sep 2025 17:33:24 +0330 Subject: [PATCH 008/100] handle selecting tabs and overflow --- .../workflow-tab/workflow-tab.component.html | 4 +- .../workflow-tab/workflow-tab.component.scss | 21 +++++++++-- .../workflow-tab/workflow-tab.component.ts | 10 ++++- .../workflows-tabs-management.component.html | 2 +- .../workflows-tabs-management.component.scss | 34 +++++++++++++---- .../workflows-tabs-management.component.ts | 6 ++- .../dashboard/dashboard.component.html | 33 ++++++++++++----- .../dashboard/dashboard.component.scss | 23 +++++++++--- .../dashboard/models/workflow.model.ts | 1 + .../workflows-list-store.service.ts | 37 +++++++++---------- .../components/header/header.component.html | 1 + .../components/header/header.component.scss | 3 +- 12 files changed, 122 insertions(+), 53 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html index d1ac579d..0b2ba479 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html @@ -1,4 +1,4 @@ -
+
-
+
\ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss index 75f67397..3d3c2396 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss @@ -9,7 +9,10 @@ inline-size: 10rem; max-inline-size: 10rem; height: 2.5rem; - background-color: var(--p-surface-100); // light gray + background-color: var(--p-surface-100); + &-active { + background-color: var(--p-primary-200); + } &:hover { background-color: var(--p-primary-200); @@ -36,14 +39,17 @@ background: gray; } &-running { - background: blue; + border-top: 2px solid blue; + border-left: 2px solid blue; + animation: spinning 0.6s linear infinite; } &-completed { background: green; } &-failed { background: red; - animation: blinking 0.8s cubic-bezier(0.39, 0.575, 0.565, 1) infinite alternate-reverse; + animation: blinking 0.8s cubic-bezier(0.39, 0.575, 0.565, 1) infinite + alternate-reverse; } } @@ -72,3 +78,12 @@ opacity: 1; } } + +@keyframes spinning { + from { + transform: rotate(0); + } + to { + transform: rotate(360deg); + } +} diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts index 6d627778..ef4bc435 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts @@ -1,4 +1,4 @@ -import { Component, input } from '@angular/core'; +import { Component, input, output } from '@angular/core'; import { WorkflowInfo } from '../../../../models/workflow.model'; @Component({ @@ -8,5 +8,11 @@ import { WorkflowInfo } from '../../../../models/workflow.model'; styleUrl: './workflow-tab.component.scss' }) export class WorkflowTabComponent { - workflowInfo = input.required(); + public workflowInfo = input.required(); + public isSelected = input.required(); + public opneWorkflow = output(); + + public onOpenWorkflow(): void { + this.opneWorkflow.emit(this.workflowInfo().id); + } } diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html index 6d35a44c..37e5078d 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html @@ -1,7 +1,7 @@
@for (tab of vm().openWorkflows; track tab.id) { - + }
diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss index f1330415..e16f6c2f 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss @@ -1,9 +1,29 @@ +:host { + display: flex; + flex: 1; + min-width: 0; + max-width: 100%; +} + .tabs-bar { + width: 100%; + display: flex; + justify-content: flex-start; + align-items: center; + gap: 0.5rem; + + &__tabs { display: flex; - align-items: center; - gap: .5rem; - &__tabs { - display: flex; - gap: 1rem; - } -} \ No newline at end of file + gap: 1rem; + max-inline-size: 70vw; + scroll-behavior: smooth; + overflow-x: auto; + } + + &__new-tab-btn { + } +} + +::-webkit-scrollbar { + display: none; +} diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts index 1596b708..285c426d 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts @@ -10,14 +10,16 @@ import { of } from 'rxjs'; }) export class WorkflowsTabsManagementComponent { public readonly vm; - // public readonly openWorkflows$; constructor(public readonly workflowListStore: WorkflowsListStore) { this.vm = this.workflowListStore.vm; - // this.openWorkflows$ = this.workflowListStore.getOpendWorkflows() } onCreateNewWorkflow():void { this.workflowListStore.createNewWorkflow(of({workflowName: "New Workflow"})); } + + openWorkflow(workflowId: string) :void { + this.workflowListStore.openWorkflow(of({workflowId})); + } } diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.html b/etl_frontend/src/app/features/dashboard/dashboard.component.html index 295f888e..c0343fd3 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.html +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.html @@ -1,13 +1,26 @@ -
- logo image - - -
-
- @if (user(); as currentUser) { - - } +
+
+ logo image + + + + +
+
+ @if (user(); as currentUser) { + + } +
- \ No newline at end of file + diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.scss b/etl_frontend/src/app/features/dashboard/dashboard.component.scss index 49dca8c2..e8c9dc4b 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.scss +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.scss @@ -1,14 +1,27 @@ -.header { - padding: 1rem; - &__toolbar { +:host { + inline-size: 100%; + + .header { + inline-size: 100%; + max-inline-size: 100%; + display: flex; + align-items: center; + justify-content: space-between; &__start { display: flex; align-items: center; gap: 1rem; + flex: 1; + &__img { - inline-size: 4rem; - block-size: 2.5rem; + inline-size: 3rem; + block-size: 2rem; } } + + &__end { + display: block; + flex-shrink: 1; + } } } diff --git a/etl_frontend/src/app/features/dashboard/models/workflow.model.ts b/etl_frontend/src/app/features/dashboard/models/workflow.model.ts index 60bfd30c..dc661de4 100644 --- a/etl_frontend/src/app/features/dashboard/models/workflow.model.ts +++ b/etl_frontend/src/app/features/dashboard/models/workflow.model.ts @@ -14,4 +14,5 @@ export interface WorkflowsListState { isLoadingWorkflows: boolean; error: string | null; isCreatingWorkflow: boolean; + loadingWorkflowId: string | null; } diff --git a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts index b94039d1..3dc22786 100644 --- a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts +++ b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts @@ -1,7 +1,7 @@ -import { effect, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { WorkflowInfo, WorkflowsListState } from '../../models/workflow.model'; -import { catchError, delay, exhaustMap, Observable, of, tap } from 'rxjs'; +import { catchError, exhaustMap, of, switchMap, tap, throwError } from 'rxjs'; const initialState: WorkflowsListState = { workflows: [{ @@ -47,7 +47,8 @@ const initialState: WorkflowsListState = { error: null, isLoadingWorkflows: false, openedWorkflowsId: ['wf_005', 'wf_004', 'wf_001'], - selectedWorkflowId: '', + selectedWorkflowId: 'wf_005', + loadingWorkflowId: null, isCreatingWorkflow: false, } @@ -56,6 +57,7 @@ export class WorkflowsListStore extends ComponentStore { constructor() { super(initialState); + //TODO handle selected workflowId at begining } public readonly vm = this.selectSignal(s => { @@ -68,29 +70,13 @@ export class WorkflowsListStore extends ComponentStore { } ); - // public loadWorkflows = this.effect(trigger$ => { //TODO call api in service to fetch list of workflows - // return trigger$ - // }) - - // public getWorkflows(): Observable { - // return of(this.vm().workflows).pipe( - // tap(() => this.patchState({ isLoadingWorkflows: true })), - // delay(3000), - // tap(() => this.patchState({ isLoadingWorkflows: false })) - // ) - // } - - // public getOpendWorkflows(): Observable { - // return of(this.vm().workflows.filter((wf) => this.vm().openedWorkflowsId.includes(wf.id))); - // } - public createNewWorkflow = this.effect<{ workflowName: string }>(workflow$ => { return workflow$.pipe( tap(() => this.patchState({ isCreatingWorkflow: true })), exhaustMap(({ workflowName }) => { // TODO: replace with actual service call return of({ - id: 'wf_' + Math.floor(Math.random() * 10000), // generate unique id + id: 'wf_' + Math.floor(Math.random() * 10000), name: workflowName, description: 'New workflow created by user.', createdAt: new Date(), @@ -114,4 +100,15 @@ export class WorkflowsListStore extends ComponentStore { ); }); + public openWorkflow = this.effect<{ workflowId: string }>(workflow$ => { + return workflow$.pipe( + tap((workflow) => this.patchState({ loadingWorkflowId: workflow.workflowId })), + switchMap((wf) => { + const index = this.vm().workflows.findIndex((workflow) => workflow.id == wf.workflowId) + return index !== -1 ? of(this.vm().workflows[index]) : throwError('can not find workflow'); + }), + tap((workflow) => this.patchState({ loadingWorkflowId: null, selectedWorkflowId: workflow.id })) + ) + }) + } \ No newline at end of file diff --git a/etl_frontend/src/app/shared/components/header/header.component.html b/etl_frontend/src/app/shared/components/header/header.component.html index a25d8980..824be7d0 100644 --- a/etl_frontend/src/app/shared/components/header/header.component.html +++ b/etl_frontend/src/app/shared/components/header/header.component.html @@ -1,4 +1,5 @@
+ diff --git a/etl_frontend/src/app/shared/components/header/header.component.scss b/etl_frontend/src/app/shared/components/header/header.component.scss index 11553063..f2a75449 100644 --- a/etl_frontend/src/app/shared/components/header/header.component.scss +++ b/etl_frontend/src/app/shared/components/header/header.component.scss @@ -1,9 +1,10 @@ .header { block-size: 100%; + max-block-size: 100%; padding-inline: 1rem; ::ng-deep .p-toolbar { padding: 0.5rem !important; } -} +} \ No newline at end of file From f42e9495380eedca807a1540f44f6479e8764e4d Mon Sep 17 00:00:00 2001 From: amirbarari Date: Mon, 1 Sep 2025 18:21:10 +0330 Subject: [PATCH 009/100] add reordering workflow tabs with drag and drop --- .../workflows-tabs-management.component.html | 23 +++- .../workflows-tabs-management.component.scss | 34 ++++++ .../workflows-tabs-management.component.ts | 14 ++- .../features/dashboard/dashboard.module.ts | 4 +- .../workflows-list-store.service.ts | 21 +++- package-lock.json | 110 ++++++++++++++++++ package.json | 5 + 7 files changed, 198 insertions(+), 13 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html index 37e5078d..fb703329 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html @@ -1,10 +1,27 @@
-
+
@for (tab of vm().openWorkflows; track tab.id) { - + }
- +
diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss index e16f6c2f..5a8d2170 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss @@ -27,3 +27,37 @@ ::-webkit-scrollbar { display: none; } + +:host ::ng-deep { + /* The element you drag (floating copy) */ + .cdk-drag-preview { + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25), + 0 0 0 3px rgba(100, 181, 246, 0.6); /* subtle blue glow */ + border-radius: 8px; + background: white; + transform: scale(1.05); + opacity: 0.95; + transition: transform 0.2s ease, box-shadow 0.2s ease; + } + + /* The "ghost" that stays behind */ + .cdk-drag-placeholder { + background: repeating-linear-gradient( + 45deg, + rgba(0, 0, 0, 0.05), + rgba(0, 0, 0, 0.05) 6px, + rgba(0, 0, 0, 0.1) 6px, + rgba(0, 0, 0, 0.1) 12px + ); + border: 2px dashed rgba(100, 181, 246, 0.6); + border-radius: 8px; + min-width: 80px; /* keeps space visible */ + opacity: 0.7; + } + + /* While items are animating into place */ + .cdk-drag-animating { + transition: transform 250ms cubic-bezier(0.25, 1, 0.5, 1); + } +} + diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts index 285c426d..eab79bc2 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { WorkflowsListStore } from '../../stores/workflows-list/workflows-list-store.service'; import { of } from 'rxjs'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; @Component({ selector: 'app-workflows-tabs-management', @@ -15,11 +16,16 @@ export class WorkflowsTabsManagementComponent { this.vm = this.workflowListStore.vm; } - onCreateNewWorkflow():void { - this.workflowListStore.createNewWorkflow(of({workflowName: "New Workflow"})); + onCreateNewWorkflow(): void { + this.workflowListStore.createNewWorkflow(of({ workflowName: "New Workflow" })); } - openWorkflow(workflowId: string) :void { - this.workflowListStore.openWorkflow(of({workflowId})); + openWorkflow(workflowId: string): void { + this.workflowListStore.openWorkflow(of({ workflowId })); + } + + onDrop(event: CdkDragDrop) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + this.workflowListStore.reorderOpenWorkflows(event.container.data); } } diff --git a/etl_frontend/src/app/features/dashboard/dashboard.module.ts b/etl_frontend/src/app/features/dashboard/dashboard.module.ts index b344dad2..73169bed 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.module.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.module.ts @@ -4,6 +4,7 @@ import { Button } from 'primeng/button'; import { Toolbar } from 'primeng/toolbar'; import { Avatar } from 'primeng/avatar'; import { DashboardComponent } from './dashboard.component'; +import { DragDropModule } from '@angular/cdk/drag-drop'; import { RouterModule } from '@angular/router'; import { dashboardRoutes } from './dashboard.module.routing'; import {HeaderComponent} from "../../shared/components/header/header.component"; @@ -23,7 +24,8 @@ import { TooltipModule } from 'primeng/tooltip'; RouterModule.forChild(dashboardRoutes), HeaderComponent, ProfilePopoverComponent, - TooltipModule + TooltipModule, + DragDropModule ], providers: [WorkflowsListStore] }) diff --git a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts index 3dc22786..325396c6 100644 --- a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts +++ b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts @@ -61,14 +61,16 @@ export class WorkflowsListStore extends ComponentStore { } public readonly vm = this.selectSignal(s => { + const workflowMap = new Map(s.workflows.map(wf => [wf.id, wf])); + + const openWorkflows = s.openedWorkflowsId + .map(id => workflowMap.get(id)) + .filter((wf): wf is WorkflowInfo => wf !== undefined); return { ...s, - openWorkflows: s.workflows.filter((wf) => - s.openedWorkflowsId.includes(wf.id) - ) + openWorkflows: openWorkflows } - } - ); + }) public createNewWorkflow = this.effect<{ workflowName: string }>(workflow$ => { return workflow$.pipe( @@ -111,4 +113,13 @@ export class WorkflowsListStore extends ComponentStore { ) }) + public readonly reorderOpenWorkflows = this.updater((state, reorderedWorkflows: WorkflowInfo[]) => { + const newOrderedIds = reorderedWorkflows.map(wf => wf.id); + + return { + ...state, + openedWorkflowsId: newOrderedIds + }; + }); + } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..e53e3f91 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,110 @@ +{ + "name": "Summer1404-Project-Team03", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@angular/cdk": "^20.2.1" + } + }, + "node_modules/@angular/cdk": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.1.tgz", + "integrity": "sha512-yEPh5hr9LZW4ey/HxtaGdSBDIkNzziLo0Dr1RP8JcxhOQ2Bzv2PZ+g8jC6aPGD7NPV8FtDf0FhTEzQr+m+gBXQ==", + "license": "MIT", + "dependencies": { + "parse5": "^8.0.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^20.0.0 || ^21.0.0", + "@angular/core": "^20.0.0 || ^21.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/common": { + "version": "20.2.3", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.2.3.tgz", + "integrity": "sha512-QLffWL8asy2oG7p3jvoNmx9s1V1WuJAm6JmQ1S8J3AN/BxumCJan49Nj8rctP8J4uwJDPQV48hqbXUdl1v7CDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.2.3", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/core": { + "version": "20.2.3", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.2.3.tgz", + "integrity": "sha512-pFMfg11X8SNNZHcLa+wy4y+eAN3FApt+wPzaxkaXaJ64c+tyHcrPNLotoWgE0jmiw8Idn4gGjKAL/WC0uw5dQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.2.3", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..0590aecf --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@angular/cdk": "^20.2.1" + } +} From 48c66beb15008b66c6d3a11c1dec45caa033951c Mon Sep 17 00:00:00 2001 From: amirbarari Date: Mon, 1 Sep 2025 19:12:26 +0330 Subject: [PATCH 010/100] add skeleton for workflow tabs --- .../workflows-tabs-management.component.html | 16 +++++++++++-- .../workflows-tabs-management.component.scss | 24 +++++++++---------- .../features/dashboard/dashboard.module.ts | 4 +++- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html index fb703329..168eba19 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html @@ -1,9 +1,19 @@
+ @if(vm().isLoadingWorkflows) { @for (index of [1, 2, 3, 4,]; track $index) { +
+ +
+ } }@else {
@for (tab of vm().openWorkflows; track tab.id) { @@ -19,9 +29,11 @@
+ }
diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss index 5a8d2170..e711e7a4 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.scss @@ -29,35 +29,33 @@ } :host ::ng-deep { - /* The element you drag (floating copy) */ .cdk-drag-preview { box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25), - 0 0 0 3px rgba(100, 181, 246, 0.6); /* subtle blue glow */ + 0 0 0 3px rgba(100, 181, 246, 0.6); border-radius: 8px; background: white; transform: scale(1.05); opacity: 0.95; transition: transform 0.2s ease, box-shadow 0.2s ease; + animation: shaking 0.5s linear infinite; } - /* The "ghost" that stays behind */ .cdk-drag-placeholder { - background: repeating-linear-gradient( - 45deg, - rgba(0, 0, 0, 0.05), - rgba(0, 0, 0, 0.05) 6px, - rgba(0, 0, 0, 0.1) 6px, - rgba(0, 0, 0, 0.1) 12px - ); - border: 2px dashed rgba(100, 181, 246, 0.6); + background-color: var(--p-surface-300); + box-sizing: border-box; border-radius: 8px; - min-width: 80px; /* keeps space visible */ + min-width: 80px; opacity: 0.7; } - /* While items are animating into place */ .cdk-drag-animating { transition: transform 250ms cubic-bezier(0.25, 1, 0.5, 1); } } +@keyframes shaking { + 0%, 100% { transform: scale(1.05) rotateZ(0deg); } + 25% { transform: scale(1.05) rotateZ(3deg); } + 50% { transform: scale(1.05) rotateZ(-3deg); } + 75% { transform: scale(1.05) rotateZ(3deg); } +} diff --git a/etl_frontend/src/app/features/dashboard/dashboard.module.ts b/etl_frontend/src/app/features/dashboard/dashboard.module.ts index 73169bed..1b77ba29 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.module.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.module.ts @@ -13,6 +13,7 @@ import { WorkflowsListStore } from './stores/workflows-list/workflows-list-store import { WorkflowsTabsManagementComponent } from './components/workflows-tabs-management/workflows-tabs-management.component'; import { WorkflowTabComponent } from './components/workflows-tabs-management/components/workflow-tab/workflow-tab.component'; import { TooltipModule } from 'primeng/tooltip'; +import { SkeletonModule } from 'primeng/skeleton'; @NgModule({ declarations: [DashboardComponent, WorkflowsTabsManagementComponent, WorkflowTabComponent], @@ -25,7 +26,8 @@ import { TooltipModule } from 'primeng/tooltip'; HeaderComponent, ProfilePopoverComponent, TooltipModule, - DragDropModule + DragDropModule, + SkeletonModule ], providers: [WorkflowsListStore] }) From 44a5a0aa032a7824162bf49d47254e605167552c Mon Sep 17 00:00:00 2001 From: amirbarari Date: Mon, 1 Sep 2025 19:33:05 +0330 Subject: [PATCH 011/100] handle closing workflows --- .../workflow-tab/workflow-tab.component.html | 22 +++++++++++--- .../workflow-tab/workflow-tab.component.ts | 5 ++++ .../workflows-tabs-management.component.html | 1 + .../workflows-tabs-management.component.ts | 4 +++ .../workflows-list-store.service.ts | 29 +++++++++++++++++++ 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html index 0b2ba479..8cda13b1 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.html @@ -1,10 +1,24 @@ -
+
-
{{ workflowInfo().name }}
+
+ {{ workflowInfo().name }} +
- +
-
\ No newline at end of file +
diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts index ef4bc435..e3f7cd8f 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts @@ -11,8 +11,13 @@ export class WorkflowTabComponent { public workflowInfo = input.required(); public isSelected = input.required(); public opneWorkflow = output(); + public closeWorkflow = output(); public onOpenWorkflow(): void { this.opneWorkflow.emit(this.workflowInfo().id); } + + public onCloseWorkflow(): void { + this.closeWorkflow.emit(this.workflowInfo().id); + } } diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html index 168eba19..51075aab 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html @@ -19,6 +19,7 @@ @for (tab of vm().openWorkflows; track tab.id) { { }; }); +public readonly closeWorkflow = this.updater( + (state, workflowIdToClose: string) => { + const idx = state.openedWorkflowsId.indexOf(workflowIdToClose); + const newOpenedIds = state.openedWorkflowsId.filter(id => id !== workflowIdToClose); + + if (state.selectedWorkflowId !== workflowIdToClose) { + return { + ...state, + openedWorkflowsId: newOpenedIds, + }; + } + + let newSelectedId: string | null = null; + if (newOpenedIds.length > 0) { + if (idx < newOpenedIds.length) { + newSelectedId = newOpenedIds[idx]; + } else { + newSelectedId = newOpenedIds[idx - 1]; + } + } + + return { + ...state, + openedWorkflowsId: newOpenedIds, + selectedWorkflowId: newSelectedId, + }; + } +); + } \ No newline at end of file From 6e283b7c0334596ebd95e4d02b46594bfac4c2eb Mon Sep 17 00:00:00 2001 From: amirbarari Date: Mon, 1 Sep 2025 19:54:22 +0330 Subject: [PATCH 012/100] adding animation for adding and removing workflow tabs --- etl_frontend/src/app/app.config.ts | 1 - .../workflow-tab/workflow-tab.component.scss | 14 ++++++++++++++ .../workflow-tab/workflow-tab.component.ts | 2 +- .../workflows-tabs-management.component.html | 1 + .../workflows-tabs-management.component.ts | 19 ++++++++++++++++++- 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/etl_frontend/src/app/app.config.ts b/etl_frontend/src/app/app.config.ts index ba67c919..3658c408 100644 --- a/etl_frontend/src/app/app.config.ts +++ b/etl_frontend/src/app/app.config.ts @@ -18,7 +18,6 @@ export const appConfig: ApplicationConfig = { provideAnimationsAsync(), provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), - provideAnimationsAsync(), providePrimeNG({ theme: { preset: CustomAura, diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss index 3d3c2396..e27ee117 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.scss @@ -1,5 +1,6 @@ .tab { display: flex; + position: relative; align-items: center; justify-content: space-evenly; cursor: pointer; @@ -10,6 +11,8 @@ max-inline-size: 10rem; height: 2.5rem; background-color: var(--p-surface-100); + // animation: swip-up-right 0.2s linear; + &-active { background-color: var(--p-primary-200); } @@ -87,3 +90,14 @@ transform: rotate(360deg); } } + +@keyframes swip-up-right { + from { + opacity: 0; + transform: translateX(-2rem) scale(0.8); + } + to { + opacity: 1; + transform: translateX(0) scale(1); + } +} diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts index e3f7cd8f..9d3c2013 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/components/workflow-tab/workflow-tab.component.ts @@ -5,7 +5,7 @@ import { WorkflowInfo } from '../../../../models/workflow.model'; selector: 'app-workflow-tab', standalone: false, templateUrl: './workflow-tab.component.html', - styleUrl: './workflow-tab.component.scss' + styleUrl: './workflow-tab.component.scss', }) export class WorkflowTabComponent { public workflowInfo = input.required(); diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html index 51075aab..7049d11e 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html @@ -23,6 +23,7 @@ (opneWorkflow)="openWorkflow($event)" [workflowInfo]="tab" [isSelected]="vm().selectedWorkflowId === tab.id ? true : false" + [@tabAnimation] > }
diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts index e8ccb554..24019277 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts @@ -2,12 +2,29 @@ import { Component } from '@angular/core'; import { WorkflowsListStore } from '../../stores/workflows-list/workflows-list-store.service'; import { of } from 'rxjs'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { + trigger, + transition, + style, + animate, +} from '@angular/animations'; @Component({ selector: 'app-workflows-tabs-management', standalone: false, templateUrl: './workflows-tabs-management.component.html', - styleUrl: './workflows-tabs-management.component.scss' + styleUrl: './workflows-tabs-management.component.scss', + animations: [ + trigger('tabAnimation', [ + transition(':enter', [ + style({ opacity: 0, transform: 'translateX(-1rem) scale(0.7)' }), + animate('300ms ease-out', style({ opacity: 1, transform: 'translateX(0) scale(1)' })), + ]), + transition(':leave', [ + animate('300ms ease-in', style({ opacity: 0, transform: 'translateX(-1rem) scale(0.7)' })), + ]), + ]), + ], }) export class WorkflowsTabsManagementComponent { public readonly vm; From c0445793410a0a8da80fbd57ce40016ab10cac1e Mon Sep 17 00:00:00 2001 From: amirbarari Date: Mon, 1 Sep 2025 23:23:02 +0330 Subject: [PATCH 013/100] crate and implement workflow selector component --- .../workflow-selector.component.html | 15 ++++++++++++ .../workflow-selector.component.scss | 0 .../workflow-selector.component.spec.ts | 23 +++++++++++++++++++ .../workflow-selector.component.ts | 23 +++++++++++++++++++ .../dashboard/dashboard.component.html | 5 ++-- .../features/dashboard/dashboard.module.ts | 15 ++++++++---- 6 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.ts diff --git a/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.html b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.html new file mode 100644 index 00000000..b07c6ec0 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.html @@ -0,0 +1,15 @@ + + + + diff --git a/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.scss b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.spec.ts new file mode 100644 index 00000000..e80db581 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkflowSelectorComponent } from './workflow-selector.component'; + +describe('WorkflowSelectorComponent', () => { + let component: WorkflowSelectorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WorkflowSelectorComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(WorkflowSelectorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.ts b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.ts new file mode 100644 index 00000000..8e45e8ef --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { WorkflowsListStore } from '../../stores/workflows-list/workflows-list-store.service'; +import { WorkflowInfo } from '../../models/workflow.model'; +import { of } from 'rxjs'; +import { ListboxChangeEvent } from 'primeng/listbox'; + +@Component({ + selector: 'app-workflow-selector', + standalone: false, + templateUrl: './workflow-selector.component.html', + styleUrl: './workflow-selector.component.scss' +}) +export class WorkflowSelectorComponent { + public readonly vm; + + constructor(private readonly workflowsListStore: WorkflowsListStore) { + this.vm = this.workflowsListStore.vm; + } + +change(event: ListboxChangeEvent): void { + this.workflowsListStore.openWorkflow(of({ workflowId: event.value.id })); +} +} diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.html b/etl_frontend/src/app/features/dashboard/dashboard.component.html index c0343fd3..5af54874 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.html +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.html @@ -9,9 +9,8 @@ size="large" [routerLink]="['/', 'dashboard', 'home']" /> - - - + +
@if (user(); as currentUser) { diff --git a/etl_frontend/src/app/features/dashboard/dashboard.module.ts b/etl_frontend/src/app/features/dashboard/dashboard.module.ts index 1b77ba29..8e5e8f9a 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.module.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.module.ts @@ -1,22 +1,25 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { Button } from 'primeng/button'; +import { Button, ButtonModule } from 'primeng/button'; import { Toolbar } from 'primeng/toolbar'; import { Avatar } from 'primeng/avatar'; import { DashboardComponent } from './dashboard.component'; import { DragDropModule } from '@angular/cdk/drag-drop'; import { RouterModule } from '@angular/router'; import { dashboardRoutes } from './dashboard.module.routing'; -import {HeaderComponent} from "../../shared/components/header/header.component"; -import {ProfilePopoverComponent} from './components/profile-popover/profile-popover.component'; +import { HeaderComponent } from "../../shared/components/header/header.component"; +import { ProfilePopoverComponent } from './components/profile-popover/profile-popover.component'; import { WorkflowsListStore } from './stores/workflows-list/workflows-list-store.service'; import { WorkflowsTabsManagementComponent } from './components/workflows-tabs-management/workflows-tabs-management.component'; import { WorkflowTabComponent } from './components/workflows-tabs-management/components/workflow-tab/workflow-tab.component'; import { TooltipModule } from 'primeng/tooltip'; import { SkeletonModule } from 'primeng/skeleton'; +import { WorkflowSelectorComponent } from './components/workflow-selector/workflow-selector.component'; +import { PopoverModule } from 'primeng/popover'; +import { ListboxModule } from 'primeng/listbox'; @NgModule({ - declarations: [DashboardComponent, WorkflowsTabsManagementComponent, WorkflowTabComponent], + declarations: [DashboardComponent, WorkflowsTabsManagementComponent, WorkflowTabComponent, WorkflowSelectorComponent], imports: [ CommonModule, Button, @@ -27,7 +30,9 @@ import { SkeletonModule } from 'primeng/skeleton'; ProfilePopoverComponent, TooltipModule, DragDropModule, - SkeletonModule + SkeletonModule, + PopoverModule, + ListboxModule, ], providers: [WorkflowsListStore] }) From 03cda636b6b5f878a519c870c0569e2bcc9ae2dd Mon Sep 17 00:00:00 2001 From: amirbarari Date: Mon, 1 Sep 2025 23:49:28 +0330 Subject: [PATCH 014/100] fix openWorkflow effect in workflows list Store --- .../workflows-list-store.service.ts | 80 ++++++++++++------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts index d1f142f0..fa3d4f8c 100644 --- a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts +++ b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts @@ -104,14 +104,34 @@ export class WorkflowsListStore extends ComponentStore { public openWorkflow = this.effect<{ workflowId: string }>(workflow$ => { return workflow$.pipe( - tap((workflow) => this.patchState({ loadingWorkflowId: workflow.workflowId })), - switchMap((wf) => { - const index = this.vm().workflows.findIndex((workflow) => workflow.id == wf.workflowId) - return index !== -1 ? of(this.vm().workflows[index]) : throwError('can not find workflow'); - }), - tap((workflow) => this.patchState({ loadingWorkflowId: null, selectedWorkflowId: workflow.id })) - ) - }) + tap(({ workflowId }) => + this.patchState({ loadingWorkflowId: workflowId }) + ), + switchMap(({ workflowId }) => { + const workflow = this.get().workflows.find(wf => wf.id === workflowId); + + if (!workflow) { + return throwError(() => new Error('Cannot find workflow')).pipe( + catchError(err => { + this.patchState({ loadingWorkflowId: null }); + return throwError(() => err); + }) + ); + } + + return of(workflow).pipe( + tap(wf => + this.patchState((state) => ({ + loadingWorkflowId: null, + selectedWorkflowId: wf.id, + openedWorkflowsId: [...state.openedWorkflowsId, wf.id] + })) + ) + ); + }) + ); + }); + public readonly reorderOpenWorkflows = this.updater((state, reorderedWorkflows: WorkflowInfo[]) => { const newOrderedIds = reorderedWorkflows.map(wf => wf.id); @@ -122,33 +142,33 @@ export class WorkflowsListStore extends ComponentStore { }; }); -public readonly closeWorkflow = this.updater( - (state, workflowIdToClose: string) => { - const idx = state.openedWorkflowsId.indexOf(workflowIdToClose); - const newOpenedIds = state.openedWorkflowsId.filter(id => id !== workflowIdToClose); + public readonly closeWorkflow = this.updater( + (state, workflowIdToClose: string) => { + const idx = state.openedWorkflowsId.indexOf(workflowIdToClose); + const newOpenedIds = state.openedWorkflowsId.filter(id => id !== workflowIdToClose); + + if (state.selectedWorkflowId !== workflowIdToClose) { + return { + ...state, + openedWorkflowsId: newOpenedIds, + }; + } + + let newSelectedId: string | null = null; + if (newOpenedIds.length > 0) { + if (idx < newOpenedIds.length) { + newSelectedId = newOpenedIds[idx]; + } else { + newSelectedId = newOpenedIds[idx - 1]; + } + } - if (state.selectedWorkflowId !== workflowIdToClose) { return { ...state, openedWorkflowsId: newOpenedIds, + selectedWorkflowId: newSelectedId, }; } - - let newSelectedId: string | null = null; - if (newOpenedIds.length > 0) { - if (idx < newOpenedIds.length) { - newSelectedId = newOpenedIds[idx]; - } else { - newSelectedId = newOpenedIds[idx - 1]; - } - } - - return { - ...state, - openedWorkflowsId: newOpenedIds, - selectedWorkflowId: newSelectedId, - }; - } -); + ); } \ No newline at end of file From 4aaf5e2eb282daa1e8d1915ba62e69e426598149 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Tue, 2 Sep 2025 00:47:24 +0330 Subject: [PATCH 015/100] combining create and select workflow together and add input to user enter the workflow name when it create --- .../workflow-selector.component.html | 60 +++++++++++++++---- .../workflow-selector.component.scss | 13 ++++ .../workflow-selector.component.ts | 10 +++- .../workflows-tabs-management.component.html | 8 --- .../workflows-tabs-management.component.ts | 6 +- .../dashboard/dashboard.component.html | 2 +- .../features/dashboard/dashboard.module.ts | 4 ++ 7 files changed, 75 insertions(+), 28 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.html b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.html index b07c6ec0..f7b9ab62 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.html @@ -1,15 +1,49 @@ - + + + + + - +
+
+ + +
+ +
diff --git a/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.scss b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.scss index e69de29b..b6011a52 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.scss @@ -0,0 +1,13 @@ +.popover-content { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding: 0.5rem; + min-width: 220px; +} + +.new-workflow { + display: flex; + gap: 0.5rem; + align-items: center; +} diff --git a/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.ts b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.ts index 8e45e8ef..2bd9e280 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflow-selector/workflow-selector.component.ts @@ -17,7 +17,11 @@ export class WorkflowSelectorComponent { this.vm = this.workflowsListStore.vm; } -change(event: ListboxChangeEvent): void { - this.workflowsListStore.openWorkflow(of({ workflowId: event.value.id })); -} + onSelectWorkflow(event: ListboxChangeEvent): void { + this.workflowsListStore.openWorkflow(of({ workflowId: event.value.id })); + } + + onCreateNewWorkflow(wfName?: string): void { + this.workflowsListStore.createNewWorkflow(of({ workflowName: wfName ? wfName : "New Workflow" })); + } } diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html index 7049d11e..9e3cd70c 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.html @@ -28,14 +28,6 @@ }
-
}
diff --git a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts index 24019277..02a89e3e 100644 --- a/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/workflows-tabs-management/workflows-tabs-management.component.ts @@ -33,9 +33,9 @@ export class WorkflowsTabsManagementComponent { this.vm = this.workflowListStore.vm; } - onCreateNewWorkflow(): void { - this.workflowListStore.createNewWorkflow(of({ workflowName: "New Workflow" })); - } + // onCreateNewWorkflow(wfName?: string): void { + // this.workflowListStore.createNewWorkflow(of({ workflowName: wfName ? wfName: "New Workflow" })); + // } openWorkflow(workflowId: string): void { this.workflowListStore.openWorkflow(of({ workflowId })); diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.html b/etl_frontend/src/app/features/dashboard/dashboard.component.html index 5af54874..7d42ac6f 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.html +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.html @@ -9,8 +9,8 @@ size="large" [routerLink]="['/', 'dashboard', 'home']" /> - +
@if (user(); as currentUser) { diff --git a/etl_frontend/src/app/features/dashboard/dashboard.module.ts b/etl_frontend/src/app/features/dashboard/dashboard.module.ts index 8e5e8f9a..e7b6f16b 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.module.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.module.ts @@ -17,6 +17,8 @@ import { SkeletonModule } from 'primeng/skeleton'; import { WorkflowSelectorComponent } from './components/workflow-selector/workflow-selector.component'; import { PopoverModule } from 'primeng/popover'; import { ListboxModule } from 'primeng/listbox'; +import { ButtonGroupModule } from 'primeng/buttongroup'; +import { InputTextModule } from 'primeng/inputtext'; @NgModule({ declarations: [DashboardComponent, WorkflowsTabsManagementComponent, WorkflowTabComponent, WorkflowSelectorComponent], @@ -33,6 +35,8 @@ import { ListboxModule } from 'primeng/listbox'; SkeletonModule, PopoverModule, ListboxModule, + ButtonGroupModule, + InputTextModule ], providers: [WorkflowsListStore] }) From 2a579aca21e2f7810fea6d323475f8e38ba53f90 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Tue, 2 Sep 2025 00:56:47 +0330 Subject: [PATCH 016/100] fix openworkflow effect in workflow-list store --- .../workflows-list-store.service.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts index fa3d4f8c..73bda87a 100644 --- a/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts +++ b/etl_frontend/src/app/features/dashboard/stores/workflows-list/workflows-list-store.service.ts @@ -121,11 +121,20 @@ export class WorkflowsListStore extends ComponentStore { return of(workflow).pipe( tap(wf => - this.patchState((state) => ({ - loadingWorkflowId: null, - selectedWorkflowId: wf.id, - openedWorkflowsId: [...state.openedWorkflowsId, wf.id] - })) + this.patchState((state) => { + if (state.openedWorkflowsId.includes(wf.id)) { + return { + loadingWorkflowId: null, + selectedWorkflowId: wf.id, + }; + } + + return { + loadingWorkflowId: null, + selectedWorkflowId: wf.id, + openedWorkflowsId: [...state.openedWorkflowsId, wf.id], + }; + }) ) ); }) From 383c0ad424c97089887aa9c9bf0b948c5b555421 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Tue, 2 Sep 2025 23:56:15 +0330 Subject: [PATCH 017/100] implementing manage files page --- .../components/home/home.component.html | 8 +- .../components/home/home.component.scss | 15 +++ .../components/home/home.component.ts | 21 ++- .../dashboard/components/home/home.module.ts | 15 ++- .../manage-files/manage-files.component.html | 102 +++++++++++++- .../manage-files/manage-files.component.scss | 22 +++ .../manage-files/manage-files.component.ts | 125 ++++++++++++++++++ .../dashboard/dashboard.component.html | 9 +- .../dashboard/dashboard.component.scss | 7 - .../features/dashboard/dashboard.component.ts | 18 --- .../features/dashboard/dashboard.module.ts | 3 +- 11 files changed, 304 insertions(+), 41 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.component.html b/etl_frontend/src/app/features/dashboard/components/home/home.component.html index 71eb9765..d9851046 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/home.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/home.component.html @@ -1,2 +1,6 @@ -

home works!

- \ No newline at end of file +
+ +
+
+ +
diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.component.scss b/etl_frontend/src/app/features/dashboard/components/home/home.component.scss index e69de29b..c027b8d6 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/home.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/home.component.scss @@ -0,0 +1,15 @@ +:host { + padding: 1.5rem; + display: flex; + justify-content: space-between; + // block-size: 100vh; + // overflow-y: ; + + .sidebar { + inline-size: 100vh; + max-inline-size: 20rem; + } + .dynamic-content { + flex: 1; + } +} \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.component.ts b/etl_frontend/src/app/features/dashboard/components/home/home.component.ts index 7b8046ab..0ccfeb67 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/home.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/home.component.ts @@ -1,11 +1,28 @@ import { Component } from '@angular/core'; +import { MenuItem } from 'primeng/api'; @Component({ selector: 'app-home', standalone: false, templateUrl: './home.component.html', - styleUrl: './home.component.scss' + styleUrl: './home.component.scss', }) export class HomeComponent { - + public readonly menuItems: MenuItem[] = [ + { + label: 'DataWave', + items: [ + { + label: 'Workflow History', + icon: 'pi pi-file-plus', + routerLink: '/dashboard/home/workflow', + }, + { + label: 'Data Management', + icon: 'pi pi-file-import', + routerLink: '/dashboard/home/files', + }, + ], + }, + ]; } diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.module.ts b/etl_frontend/src/app/features/dashboard/components/home/home.module.ts index 07254148..12e8cfce 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/home.module.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/home.module.ts @@ -4,8 +4,14 @@ import { HomeComponent } from './home.component'; import { RouterModule } from '@angular/router'; import { homeRoutes } from './home.module.routing'; import { ManageFilesComponent } from './manage-files/manage-files.component'; -import { manageUserRoutes } from '../../../profile/components/manage-user/manage-user.module.routing'; import { ManageWorkflowsComponent } from './manage-workflows/manage-workflows.component'; +import { MenuModule } from 'primeng/menu'; +import { FormsModule } from '@angular/forms'; +import { CardModule } from 'primeng/card'; +import { TableModule } from 'primeng/table'; +import { ButtonModule } from 'primeng/button'; +import { DropdownModule } from 'primeng/dropdown'; +import { FileUploadModule } from 'primeng/fileupload'; @@ -15,6 +21,13 @@ import { ManageWorkflowsComponent } from './manage-workflows/manage-workflows.co CommonModule, RouterModule.forChild(homeRoutes), RouterModule, + MenuModule, + FormsModule, + CardModule, + TableModule, + ButtonModule, + FileUploadModule, + DropdownModule, ] }) export class HomeModule { } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html index 78469913..40a6cd81 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html @@ -1 +1,101 @@ -

manage-files works!

+ + + +
+ + +
+
+ +
+ + + + + + + File Name + Size + Last Modified + Actions + + + + + {{ file.name }} + {{ file.size }} + {{ file.lastModified }} + + + + + + + + + No files found. Please upload a CSV file. + + + +
+ +
+

Viewing File Content

+ + + + {{ col }} + + + + + + {{ rowData[col] }} + + + + +
+ +
+

Configure Column Types

+

Please assign a data type to each column from your uploaded file.

+ + + + + Column Header + Data Type + + + + + {{ column.header }} + + + + + + + + +
+ +
+
+
\ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss index e69de29b..371a924d 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss @@ -0,0 +1,22 @@ +// Use ::ng-deep for component-specific overrides if needed, but PrimeNG's styleClass is often better. +// Example: Add margin to the card for spacing +:host { + display: block; + padding: 1.5rem; + overflow-y: auto; +} + +// Add some spacing between action buttons +.p-mr-2 { + margin-right: 0.5rem; +} + +.p-mt-4 { + margin-top: 1.5rem; +} + +// Ensure the file upload component takes up reasonable space +p-fileUpload { + display: block; + margin-bottom: 1.5rem; +} \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.ts index 620e9d7c..a1256f87 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.ts @@ -1,4 +1,11 @@ import { Component } from '@angular/core'; +import { MessageService } from 'primeng/api'; + + +interface CsvColumn { + header: string; + selectedType: string; +} @Component({ selector: 'app-manage-files', @@ -7,5 +14,123 @@ import { Component } from '@angular/core'; styleUrl: './manage-files.component.scss' }) export class ManageFilesComponent { + // --- STATE MANAGEMENT --- + files: any[] = []; // List of all uploaded files + selectedFileContent: { headers: string[], data: any[] } | null = null; + uploadedFileColumns: CsvColumn[] = []; + + // Available database types for column mapping + dbTypes = [ + { label: 'Text', value: 'TEXT' }, + { label: 'Integer', value: 'INTEGER' }, + { label: 'Decimal', value: 'DECIMAL' }, + { label: 'Date', value: 'DATE' }, + { label: 'Boolean', value: 'BOOLEAN' } + ]; + + constructor(private messageService: MessageService) { } + + ngOnInit() { + // Mock initial file list (in a real app, you'd fetch this from a server) + this.files = [ + { name: 'products.csv', size: '2.5 KB', lastModified: '2025-08-15' }, + { name: 'customer_data.csv', size: '10.1 KB', lastModified: '2025-08-20' }, + ]; + } + + // --- FILE LIST ACTIONS --- + + /** + * Deletes a file from the list. + * @param fileName The name of the file to delete. + */ + deleteFile(fileName: string) { + this.files = this.files.filter(f => f.name !== fileName); + this.messageService.add({ severity: 'warn', summary: 'Deleted', detail: `File '${fileName}' removed.` }); + // If the deleted file was being viewed, clear the view + if (this.selectedFileContent && fileName === 'currently_viewed_file.csv') { // Mock logic + this.selectedFileContent = null; + } + } + + /** + * Displays the content of a selected CSV file as a table. + * @param file The file object to view. + */ + viewFile(file: any) { + // In a real app, you would fetch and parse the file content from your server. + // Here we'll just mock the data for demonstration. + this.selectedFileContent = { + headers: ['ID', 'ProductName', 'Price', 'Stock'], + data: [ + { ID: 1, ProductName: 'Laptop', Price: 1200, Stock: 50 }, + { ID: 2, ProductName: 'Mouse', Price: 25, Stock: 300 }, + { ID: 3, ProductName: 'Keyboard', Price: 75, Stock: 150 } + ] + }; + this.uploadedFileColumns = []; // Hide the upload view + } + + // --- FILE UPLOAD AND PROCESSING --- + + /** + * Handles the file upload event. Reads the CSV and prepares it for type mapping. + * @param event The upload event containing the file. + */ + onFileUpload(event: any) { + const file = event.files[0]; + const reader = new FileReader(); + + reader.onload = (e) => { + const text = reader.result as string; + const lines = text.split(/\r\n|\n/).filter(line => line.trim() !== ''); // Split lines and remove empty ones + + if (lines.length > 0) { + const headers = lines[0].split(','); + this.uploadedFileColumns = headers.map(header => ({ + header: header.trim(), + selectedType: 'TEXT' // Default to TEXT + })); + + // Add the new file to our list + this.files.push({ + name: file.name, + size: `${(file.size / 1024).toFixed(2)} KB`, + lastModified: new Date().toISOString().split('T')[0] + }); + + this.selectedFileContent = null; // Hide file view + this.messageService.add({ severity: 'success', summary: 'Success', detail: 'File uploaded and parsed.' }); + } else { + this.messageService.add({ severity: 'error', summary: 'Error', detail: 'The uploaded file is empty.' }); + } + }; + + reader.onerror = () => { + this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Failed to read file.' }); + }; + + reader.readAsText(file); + } + + /** + * Finalizes the column type selection. + */ + confirmColumnTypes() { + // In a real app, you would send `this.uploadedFileColumns` to your backend + // to save the mapping and process the data. + console.log('Final column mapping:', this.uploadedFileColumns); + this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Column types have been set.' }); + + // Clear the view after confirming + this.uploadedFileColumns = []; + } + /** + * Returns to the main file list view. + */ + goBack() { + this.selectedFileContent = null; + this.uploadedFileColumns = []; + } } diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.html b/etl_frontend/src/app/features/dashboard/dashboard.component.html index 23d52009..318e8d48 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.html +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.html @@ -22,12 +22,5 @@
-<<<<<<< HEAD - -======= - -
- -
->>>>>>> 4897728c253eae240bd0bf2a3a48719643729a6f + diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.scss b/etl_frontend/src/app/features/dashboard/dashboard.component.scss index e9801681..a72ff714 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.scss +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.scss @@ -26,10 +26,3 @@ } } -.dashboard { - &__sidebar { - inline-size: 20vw; - block-size: 100vh; - } -} - diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.ts b/etl_frontend/src/app/features/dashboard/dashboard.component.ts index b773e791..db5d5cdb 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.ts @@ -13,24 +13,6 @@ export class DashboardComponent implements OnInit { public readonly user = computed(() => this.userStore.vm().user); public readonly isLoading = computed(() => this.userStore.vm().isLoading); - public readonly menuItems: MenuItem[] = [ - { - label: 'DataWave', - items: [ - { - label: 'Workflow History', - icon: 'pi pi-file-plus', - routerLink: '/profile', - }, - { - label: 'Data Management', - icon: 'pi pi-file-import', - routerLink: '/profile', - } - ] - } - ]; - constructor(private userStore: UserStoreService) {} ngOnInit() { diff --git a/etl_frontend/src/app/features/dashboard/dashboard.module.ts b/etl_frontend/src/app/features/dashboard/dashboard.module.ts index 3ea26514..24704a48 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.module.ts +++ b/etl_frontend/src/app/features/dashboard/dashboard.module.ts @@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'; import { Button, ButtonModule } from 'primeng/button'; import { Toolbar } from 'primeng/toolbar'; import { Avatar } from 'primeng/avatar'; -import { Menu } from 'primeng/menu'; +import { Menu, MenuModule } from 'primeng/menu'; import { DashboardComponent } from './dashboard.component'; import { DragDropModule } from '@angular/cdk/drag-drop'; import { RouterModule } from '@angular/router'; @@ -38,7 +38,6 @@ import { InputTextModule } from 'primeng/inputtext'; ListboxModule, ButtonGroupModule, InputTextModule, - Menu, ], providers: [WorkflowsListStore] }) From f11526297ac409f7d13c50a633c55a86420205cc Mon Sep 17 00:00:00 2001 From: amirbarari Date: Wed, 3 Sep 2025 00:48:17 +0330 Subject: [PATCH 018/100] make manage-files component modular --- .../components/home/home.component.ts | 5 ++-- .../components/home/home.module.routing.ts | 16 +++++----- .../dashboard/components/home/home.module.ts | 15 +--------- .../home/manage-files/manage-files.module.ts | 29 +++++++++++++++++++ .../home/manage-files/manage-files.routing.ts | 20 +++++++++++++ .../upload-file/upload-file.component.html | 1 + .../upload-file/upload-file.component.scss | 0 .../upload-file/upload-file.component.spec.ts | 23 +++++++++++++++ .../upload-file/upload-file.component.ts | 11 +++++++ 9 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.module.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.ts diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.component.ts b/etl_frontend/src/app/features/dashboard/components/home/home.component.ts index 0ccfeb67..2ff1afb9 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/home.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/home.component.ts @@ -15,12 +15,13 @@ export class HomeComponent { { label: 'Workflow History', icon: 'pi pi-file-plus', - routerLink: '/dashboard/home/workflow', + routerLink: ['/dashboard/home/workflows'], + routerLinkActiveOptions: { exact: true }, }, { label: 'Data Management', icon: 'pi pi-file-import', - routerLink: '/dashboard/home/files', + routerLink: ['/dashboard/home/files'], }, ], }, diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts b/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts index ddf5f04d..091a0648 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/home.module.routing.ts @@ -12,18 +12,20 @@ export const homeRoutes: Routes = [ { path: '', pathMatch: 'full', - redirectTo: 'workflows' + redirectTo: 'workflows', }, { path: 'workflows', - component: ManageWorkflowsComponent + component: ManageWorkflowsComponent, // loadComponent: () => import('./manage-workflows/manage-workflows.component').then(m => m.ManageWorkflowsComponent) }, { path: 'files', - component: ManageFilesComponent, - // loadComponent: () => import('./manage-files/manage-files.component').then(m => m.ManageFilesComponent) - } - ] + loadChildren: () => + import('./manage-files/manage-files.module').then( + (m) => m.ManageFilesModule + ), + }, + ], }, -] \ No newline at end of file +]; \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.module.ts b/etl_frontend/src/app/features/dashboard/components/home/home.module.ts index 12e8cfce..d7f1a9a6 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/home.module.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/home.module.ts @@ -3,31 +3,18 @@ import { CommonModule } from '@angular/common'; import { HomeComponent } from './home.component'; import { RouterModule } from '@angular/router'; import { homeRoutes } from './home.module.routing'; -import { ManageFilesComponent } from './manage-files/manage-files.component'; import { ManageWorkflowsComponent } from './manage-workflows/manage-workflows.component'; import { MenuModule } from 'primeng/menu'; -import { FormsModule } from '@angular/forms'; -import { CardModule } from 'primeng/card'; -import { TableModule } from 'primeng/table'; -import { ButtonModule } from 'primeng/button'; -import { DropdownModule } from 'primeng/dropdown'; -import { FileUploadModule } from 'primeng/fileupload'; @NgModule({ - declarations: [HomeComponent, ManageFilesComponent, ManageWorkflowsComponent], + declarations: [HomeComponent, ManageWorkflowsComponent], imports: [ CommonModule, RouterModule.forChild(homeRoutes), RouterModule, MenuModule, - FormsModule, - CardModule, - TableModule, - ButtonModule, - FileUploadModule, - DropdownModule, ] }) export class HomeModule { } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.module.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.module.ts new file mode 100644 index 00000000..9a669e00 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ManageFilesComponent } from './manage-files.component'; +import { FormsModule } from '@angular/forms'; +import { CardModule } from 'primeng/card'; +import { TableModule } from 'primeng/table'; +import { ButtonModule } from 'primeng/button'; +import { FileUploadModule } from 'primeng/fileupload'; +import { DropdownModule } from 'primeng/dropdown'; +import { RouterModule } from '@angular/router'; +import { manageFilesRoutes } from './manage-files.routing'; + + + +@NgModule({ + declarations: [ManageFilesComponent], + imports: [ + CommonModule, + FormsModule, + CardModule, + TableModule, + ButtonModule, + FileUploadModule, + DropdownModule, + RouterModule, + RouterModule.forChild(manageFilesRoutes) + ] +}) +export class ManageFilesModule { } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts new file mode 100644 index 00000000..8d34f2da --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts @@ -0,0 +1,20 @@ +import { Routes } from "@angular/router"; +import { ManageFilesComponent } from "./manage-files.component"; +import { UploadFileComponent } from "./upload-file/upload-file.component"; + + +export const manageFilesRoutes: Routes = [ + { + path: '', + pathMatch: 'full', + redirectTo: 'list' + }, + { + path: 'list', + component: ManageFilesComponent, + }, + { + path: 'upload-file', + component: UploadFileComponent + } +] \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html new file mode 100644 index 00000000..23944364 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html @@ -0,0 +1 @@ +

upload-file works!

diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.spec.ts new file mode 100644 index 00000000..01586169 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UploadFileComponent } from './upload-file.component'; + +describe('UploadFileComponent', () => { + let component: UploadFileComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UploadFileComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(UploadFileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.ts new file mode 100644 index 00000000..df3ccfbc --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-upload-file', + imports: [], + templateUrl: './upload-file.component.html', + styleUrl: './upload-file.component.scss' +}) +export class UploadFileComponent { + +} From 8aec14cf2faa865c7b226c67e446c04008751728 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Wed, 3 Sep 2025 01:32:58 +0330 Subject: [PATCH 019/100] seprate upload-file to new component --- .../manage-files/manage-files.component.html | 141 +++++++++++------- .../home/manage-files/manage-files.module.ts | 2 - .../upload-file/upload-file.component.html | 76 +++++++++- .../upload-file/upload-file.component.scss | 29 ++++ .../upload-file/upload-file.component.ts | 118 ++++++++++++++- 5 files changed, 306 insertions(+), 60 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html index 40a6cd81..b8e465fa 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.html @@ -1,57 +1,78 @@ - - + + -
- +
+
-
- - + +
- - - - File Name - Size - Last Modified - Actions - - - - - {{ file.name }} - {{ file.size }} - {{ file.lastModified }} - - - - - - - - + + + + File Name + Size + Last Modified + Actions + + + + + {{ file.name }} + {{ file.size }} + {{ file.lastModified }} + + + + + + + + No files found. Please upload a CSV file. - - - -
- + +
+ +
+ +

Viewing File Content

- + {{ col }} @@ -66,12 +87,15 @@

Viewing File Content

- +

Configure Column Types

Please assign a data type to each column from your uploaded file.

- - + + Column Header @@ -82,20 +106,25 @@

Configure Column Types

{{ column.header }} - + optionLabel="label" + optionValue="value" + >
- +
- +
- \ No newline at end of file + diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.module.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.module.ts index 9a669e00..ddd44e12 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.module.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.module.ts @@ -5,7 +5,6 @@ import { FormsModule } from '@angular/forms'; import { CardModule } from 'primeng/card'; import { TableModule } from 'primeng/table'; import { ButtonModule } from 'primeng/button'; -import { FileUploadModule } from 'primeng/fileupload'; import { DropdownModule } from 'primeng/dropdown'; import { RouterModule } from '@angular/router'; import { manageFilesRoutes } from './manage-files.routing'; @@ -20,7 +19,6 @@ import { manageFilesRoutes } from './manage-files.routing'; CardModule, TableModule, ButtonModule, - FileUploadModule, DropdownModule, RouterModule, RouterModule.forChild(manageFilesRoutes) diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html index 23944364..de5a41cb 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html @@ -1 +1,75 @@ -

upload-file works!

+
+

Upload New Dataset

+

Select one or more CSV files. The first file will be used to generate a column preview.

+ + + +
    +
  • +
    {{ file.name }} - {{ file.size | number }} bytes
    +
  • +
+
+
+ +
+

Configure Columns

+ + + + Original Column Name + New Column Name (Editable) + Data Type + + + + + {{ col.originalHeader }} + + + + + + + + + +
+ +
+ + +
+
\ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss index e69de29b..2baf9668 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss @@ -0,0 +1,29 @@ +:host { + display: block; + font-family: var(--font-family); +} + +.upload-card { + padding: 1.5rem; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + background-color: #ffffff; +} + +.preview-container { + margin-top: 2rem; + border-top: 1px solid #dee2e6; + padding-top: 1.5rem; +} + +// Ensure table inputs are full width +:host ::ng-deep .p-datatable-sm td .p-inputtext { + width: 100%; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + gap: 0.5rem; + margin-top: 1.5rem; +} \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.ts index df3ccfbc..c546fd05 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.ts @@ -1,11 +1,127 @@ +import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { MessageService } from 'primeng/api'; +import { Button, ButtonModule } from 'primeng/button'; +import { DropdownModule } from 'primeng/dropdown'; +import { FileUpload, FileUploadModule } from 'primeng/fileupload' +import { InputTextModule } from 'primeng/inputtext'; +import { TableModule } from 'primeng/table'; + +export interface CsvColumnConfig { + originalHeader: string; + newHeader: string; + selectedType: string; +} @Component({ selector: 'app-upload-file', - imports: [], + imports: [ + CommonModule, + FormsModule, + FileUploadModule, + ButtonModule, + TableModule, + InputTextModule, + DropdownModule, + ], templateUrl: './upload-file.component.html', styleUrl: './upload-file.component.scss' }) export class UploadFileComponent { + // Stores files selected by the user, but not yet uploaded + selectedFiles: File[] = []; + + // Holds the configuration for the columns of the previewed file + columnConfigurations: CsvColumnConfig[] = []; + + // Data types for the dropdown menu in the preview table + dbTypes = [ + { label: 'Text', value: 'TEXT' }, + { label: 'Integer', value: 'INTEGER' }, + { label: 'Decimal', value: 'DECIMAL' }, + { label: 'Date', value: 'DATE' }, + { label: 'Boolean', value: 'BOOLEAN' } + ]; + + constructor(private messageService: MessageService) { } + + /** + * Triggered when files are selected in the p-fileUpload component. + * It populates the selected files list and parses the first valid CSV for preview. + * @param event The file select event. + */ + onFileSelect(event: any): void { + this.selectedFiles = [...event.files]; + + // Find the first CSV file to generate a preview + const csvFile = this.selectedFiles.find(f => f.type === 'text/csv' || f.name.endsWith('.csv')); + + if (csvFile) { + this.parseCsvForPreview(csvFile); + } else { + // Clear previous preview if no CSV is selected + this.columnConfigurations = []; + } + } + + /** + * Reads a CSV file and extracts its header row to build the configuration table. + * @param file The CSV file to parse. + */ + private parseCsvForPreview(file: File): void { + const reader = new FileReader(); + + reader.onload = (e) => { + const text = reader.result as string; + const lines = text.split(/\r\n|\n/); // Split into lines + + if (lines.length > 0) { + const headers = lines[0].split(',').map(h => h.trim()); + this.columnConfigurations = headers.map(header => ({ + originalHeader: header, + newHeader: header, // Initially, newHeader is same as original + selectedType: 'TEXT' // Default to TEXT + })); + this.messageService.add({ severity: 'info', summary: 'Preview Ready', detail: 'Configure columns for the first selected file.' }); + } + }; + + reader.onerror = () => { + this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Could not read the file.' }); + }; + + reader.readAsText(file); + } + + /** + * This is the final "upload" action. + * In a real app, you would send the files and the column configuration + * to your backend service here. + */ + processAndUploadFiles(): void { + if (this.selectedFiles.length === 0) { + this.messageService.add({ severity: 'warn', summary: 'No Files', detail: 'Please select files to upload.' }); + return; + } + + // --- Mock Backend Call --- + console.log('Uploading Files:', this.selectedFiles.map(f => f.name)); + console.log('With Column Configuration:', this.columnConfigurations); + // ------------------------- + + this.messageService.add({ severity: 'success', summary: 'Success', detail: `${this.selectedFiles.length} file(s) are being processed.` }); + + // Reset the component state after "uploading" + this.clearSelection(); + } + + /** + * Clears the selected files and the configuration preview. + */ + clearSelection(): void { + this.selectedFiles = []; + this.columnConfigurations = []; + } } From a316fa5e7a958a7a2c76914b03206f12c6890eb9 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Wed, 3 Sep 2025 10:09:00 +0330 Subject: [PATCH 020/100] fix scroll problem in home page --- .../dashboard/components/home/home.component.scss | 10 +++++----- .../home/manage-files/manage-files.component.scss | 2 +- .../upload-file/upload-file.component.scss | 4 ++-- .../app/features/dashboard/dashboard.component.scss | 1 + .../app/shared/components/header/header.component.scss | 4 ++-- etl_frontend/src/styles.scss | 4 ++++ 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/home/home.component.scss b/etl_frontend/src/app/features/dashboard/components/home/home.component.scss index c027b8d6..7f7723a3 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/home.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/home.component.scss @@ -1,15 +1,15 @@ :host { padding: 1.5rem; display: flex; - justify-content: space-between; - // block-size: 100vh; - // overflow-y: ; - + gap: 1rem; + block-size: calc(100vh - var(--header-height)); + .sidebar { inline-size: 100vh; max-inline-size: 20rem; } .dynamic-content { flex: 1; + overflow-y: auto; } -} \ No newline at end of file +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss index 371a924d..f68f81dc 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.component.scss @@ -1,7 +1,7 @@ // Use ::ng-deep for component-specific overrides if needed, but PrimeNG's styleClass is often better. // Example: Add margin to the card for spacing :host { - display: block; + // display: block; padding: 1.5rem; overflow-y: auto; } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss index 2baf9668..025445ec 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss @@ -7,12 +7,12 @@ padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - background-color: #ffffff; + // background-color: #ffffff; } .preview-container { margin-top: 2rem; - border-top: 1px solid #dee2e6; + // border-top: 1px solid #dee2e6; padding-top: 1.5rem; } diff --git a/etl_frontend/src/app/features/dashboard/dashboard.component.scss b/etl_frontend/src/app/features/dashboard/dashboard.component.scss index a72ff714..b6a60d7a 100644 --- a/etl_frontend/src/app/features/dashboard/dashboard.component.scss +++ b/etl_frontend/src/app/features/dashboard/dashboard.component.scss @@ -1,5 +1,6 @@ :host { inline-size: 100%; + block-size: 100%; .header { inline-size: 100%; diff --git a/etl_frontend/src/app/shared/components/header/header.component.scss b/etl_frontend/src/app/shared/components/header/header.component.scss index f2a75449..daa437f6 100644 --- a/etl_frontend/src/app/shared/components/header/header.component.scss +++ b/etl_frontend/src/app/shared/components/header/header.component.scss @@ -1,10 +1,10 @@ .header { - block-size: 100%; + block-size: var(--header-height); max-block-size: 100%; padding-inline: 1rem; ::ng-deep .p-toolbar { - padding: 0.5rem !important; + padding: 0.2rem 0.5rem !important; } } \ No newline at end of file diff --git a/etl_frontend/src/styles.scss b/etl_frontend/src/styles.scss index f257f81f..84247c8a 100644 --- a/etl_frontend/src/styles.scss +++ b/etl_frontend/src/styles.scss @@ -2,6 +2,10 @@ html { overflow: hidden; } +:root { + --header-height: 3rem; +} + p-toolbar .p-toolbar { padding: 1rem; border-inline: none; From 18593fd02d8247e2ca3cc0f9d0cba72eae9fcef6 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Wed, 3 Sep 2025 14:58:53 +0330 Subject: [PATCH 021/100] implement custome file uploader --- .../add-new-file/add-new-file.component.html | 1 + .../add-new-file/add-new-file.component.scss | 4 + .../add-new-file.component.spec.ts | 23 ++++++ .../add-new-file/add-new-file.component.ts | 12 +++ .../file-uploader.component.html | 42 ++++++++++ .../file-uploader.component.scss | 72 +++++++++++++++++ .../file-uploader.component.spec.ts | 23 ++++++ .../file-uploader/file-uploader.component.ts | 77 +++++++++++++++++++ .../home/manage-files/manage-files.routing.ts | 6 +- .../upload-file-store.service.spec.ts | 16 ++++ .../upload-file/upload-file-store.service.ts | 9 +++ .../upload-file/upload-file.component.html | 74 ++++++++++++------ .../upload-file/upload-file.component.scss | 23 ++++-- 13 files changed, 352 insertions(+), 30 deletions(-) create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.ts diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.html new file mode 100644 index 00000000..ec95e0ed --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.scss new file mode 100644 index 00000000..f4484518 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.scss @@ -0,0 +1,4 @@ +:host { + display: block; + padding-inline: 1rem; +} \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.spec.ts new file mode 100644 index 00000000..12259ca6 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddNewFileComponent } from './add-new-file.component'; + +describe('AddNewFileComponent', () => { + let component: AddNewFileComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AddNewFileComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AddNewFileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.ts new file mode 100644 index 00000000..fc607145 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; +import { FileUploaderComponent } from './components/file-uploader/file-uploader.component'; + +@Component({ + selector: 'app-add-new-file', + templateUrl: './add-new-file.component.html', + imports: [FileUploaderComponent], + styleUrl: './add-new-file.component.scss' +}) +export class AddNewFileComponent { + +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html new file mode 100644 index 00000000..e2994089 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html @@ -0,0 +1,42 @@ +
+ +
+ + + +
+
+ + Drop files here to upload +
+
+
+
+
diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss new file mode 100644 index 00000000..04e749c6 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss @@ -0,0 +1,72 @@ +:host { + display: block; + height: calc(90vh - var(--header-height)); + position: relative; +} + +.file-uploader { + height: 100%; + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1rem; + box-sizing: border-box; + + &__actions { + display: flex; + gap: 1rem; + align-items: center; + } + + &__zone-area { + position: relative; + flex: 1; + border: 2px dashed rgba(0, 0, 0, 0.2); + border-radius: 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: #0078d7; + font-size: 1.2rem; + cursor: pointer; + transition: background-color 0.3s, border-color 0.3s; + + &-drag-over { + background-color: rgba(0, 56, 141, 0.2); + border-color: rgba(0, 56, 141, 0.7); + } + + i { + font-size: 2rem; + margin-bottom: 0.5rem; + } + + span { + font-weight: 500; + } + } + + &__uploaded-file-list { + margin-top: 1rem; + display: flex; + flex-direction: column; + gap: 0.5rem; + + &__item { + padding: 0.5rem 1rem; + background-color: #f1f1f1; + border-radius: 0.25rem; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.95rem; + color: #333; + + .pi-times { + cursor: pointer; + color: #c00; + } + } + } +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.spec.ts new file mode 100644 index 00000000..77fc64c8 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FileUploaderComponent } from './file-uploader.component'; + +describe('FileUploaderComponent', () => { + let component: FileUploaderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FileUploaderComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FileUploaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts new file mode 100644 index 00000000..db91fc49 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts @@ -0,0 +1,77 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { Button } from 'primeng/button'; + +@Component({ + selector: 'app-file-uploader', + imports: [Button], + templateUrl: './file-uploader.component.html', + styleUrl: './file-uploader.component.scss' +}) +export class FileUploaderComponent { + @ViewChild('fileInput') fileInput!: ElementRef; + + isDragging = false; + files: File[] = []; + + onDragOver(event: DragEvent): void { + // This is crucial to allow a drop. + event.preventDefault(); + event.stopPropagation(); + this.isDragging = true; + } + + onDragLeave(event: DragEvent): void { + event.preventDefault(); + event.stopPropagation(); + this.isDragging = false; + } + + onDrop(event: DragEvent): void { + event.preventDefault(); + event.stopPropagation(); + this.isDragging = false; + + const droppedFiles = event.dataTransfer?.files; + console.log(droppedFiles); + if (droppedFiles) { + this.handleFiles(Array.from(droppedFiles)); + } + } + + // --- File Input and Selection Handlers --- + + // Triggers the hidden file input + openFilePicker(): void { + this.fileInput.nativeElement.click(); + } + + // Handles files selected via the file dialog + onFileSelect(event: Event): void { + const element = event.target as HTMLInputElement; + const selectedFiles = element.files; + if (selectedFiles) { + this.handleFiles(Array.from(selectedFiles)); + } + // Clear the input value to allow selecting the same file again + element.value = ''; + } + + // --- File Management --- + + private handleFiles(newFiles: File[]): void { + // You can add validation here (file type, size, etc.) + // For example, to prevent duplicates: + // const uniqueFiles = newFiles.filter(newFile => + // !this.files.some(existingFile => existingFile.name === newFile.name) + // ); + this.files.push(...newFiles); + } + + removeFile(fileToRemove: File): void { + this.files = this.files.filter(file => file !== fileToRemove); + } + + clearFiles(): void { + this.files = []; + } +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts index 8d34f2da..237b21b2 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts @@ -1,6 +1,6 @@ import { Routes } from "@angular/router"; import { ManageFilesComponent } from "./manage-files.component"; -import { UploadFileComponent } from "./upload-file/upload-file.component"; +import { AddNewFileComponent } from "./components/add-new-file/add-new-file.component"; export const manageFilesRoutes: Routes = [ @@ -14,7 +14,7 @@ export const manageFilesRoutes: Routes = [ component: ManageFilesComponent, }, { - path: 'upload-file', - component: UploadFileComponent + path: 'new-file', + component: AddNewFileComponent } ] \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.spec.ts new file mode 100644 index 00000000..b81c6df7 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { UploadFileStoreService } from './upload-file-store.service'; + +describe('UploadFileStoreService', () => { + let service: UploadFileStoreService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(UploadFileStoreService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.ts new file mode 100644 index 00000000..3c5d9712 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class UploadFileStoreService { + + constructor() { } +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html index de5a41cb..758af972 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.html @@ -1,25 +1,50 @@ -
+ diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss index 025445ec..975d68ab 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/upload-file.component.scss @@ -1,18 +1,31 @@ :host { display: block; - font-family: var(--font-family); + block-size: calc(90vh - var(--header-height)); } .upload-card { padding: 1.5rem; border-radius: 8px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - // background-color: #ffffff; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.205); + + &__upload-container { + // height: 100%; + + &__files-list { + // height: 100%; + + &__file { + max-width: 1rem; + background-color: red; + } + } + } + + } .preview-container { margin-top: 2rem; - // border-top: 1px solid #dee2e6; padding-top: 1.5rem; } @@ -26,4 +39,4 @@ justify-content: flex-end; gap: 0.5rem; margin-top: 1.5rem; -} \ No newline at end of file +} From 40eef95f37a15af430619a0ebebd53dc178dcb1c Mon Sep 17 00:00:00 2001 From: amirbarari Date: Wed, 3 Sep 2025 19:16:20 +0330 Subject: [PATCH 022/100] fix droping files and add styles for it in file-uploader component --- .../file-uploader.component.html | 8 ++++-- .../file-uploader.component.scss | 26 ++++++++++++++----- .../file-uploader/file-uploader.component.ts | 3 ++- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html index e2994089..ac7c891d 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html @@ -1,6 +1,5 @@
+ @if(files.length === 0 || isDragging){
Drop files here to upload
+ }
diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss index 04e749c6..a01a7c48 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss @@ -8,9 +8,8 @@ height: 100%; display: flex; flex-direction: column; - gap: 1rem; + gap: 2rem; padding: 1rem; - box-sizing: border-box; &__actions { display: flex; @@ -19,9 +18,9 @@ } &__zone-area { - position: relative; + block-size: 100%; flex: 1; - border: 2px dashed rgba(0, 0, 0, 0.2); + border: 0.2rem dashed rgba(0, 0, 0, 0.2); border-radius: 0.5rem; display: flex; flex-direction: column; @@ -33,8 +32,14 @@ transition: background-color 0.3s, border-color 0.3s; &-drag-over { - background-color: rgba(0, 56, 141, 0.2); - border-color: rgba(0, 56, 141, 0.7); + background: linear-gradient( + 120deg, + rgba(0, 56, 141, 0.1) 25%, + rgba(0, 56, 141, 0.25) 50%, + rgba(0, 56, 141, 0.1) 75% + ); + background-size: 200% 100%; + animation: bg-shimmer 1.5s linear infinite; } i { @@ -70,3 +75,12 @@ } } } + +@keyframes bg-shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts index db91fc49..ba6bc06b 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts @@ -1,9 +1,10 @@ +import { NgClass } from '@angular/common'; import { Component, ElementRef, ViewChild } from '@angular/core'; import { Button } from 'primeng/button'; @Component({ selector: 'app-file-uploader', - imports: [Button], + imports: [Button, NgClass], templateUrl: './file-uploader.component.html', styleUrl: './file-uploader.component.scss' }) From 2e22675e0bb44a85c1da423bf3272eb6ff6fb740 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Wed, 3 Sep 2025 20:45:37 +0330 Subject: [PATCH 023/100] implement uploaded-file-item component --- .../uploaded-file-item.component.html | 10 +++ .../uploaded-file-item.component.scss | 81 +++++++++++++++++++ .../uploaded-file-item.component.spec.ts | 23 ++++++ .../uploaded-file-item.component.ts | 25 ++++++ .../file-uploader.component.html | 6 +- .../file-uploader.component.scss | 1 + .../file-uploader/file-uploader.component.ts | 21 ++++- 7 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.ts diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.html new file mode 100644 index 00000000..c78c9284 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.html @@ -0,0 +1,10 @@ +
+

{{ fileName() }}

+
+ + +
+
diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.scss new file mode 100644 index 00000000..d4ac2651 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.scss @@ -0,0 +1,81 @@ +.item { + position: relative; + overflow: hidden; + width: fit-content; + min-inline-size: 5rem; + height: 2rem; + border-radius: 1rem; + border: 0.1rem solid var(--p-primary-color); + text-align: center; + display: grid; + place-items: center; + padding: 0.5rem; + cursor: pointer; + transition: all 0.3s ease-in-out; + + &:hover &__actions { + display: flex; + transition: all 0.4s ease-in-out; + } + + &__actions { + display: flex; + transition: all 0.4s ease-in-out; + position: absolute; + inset: 0; + background-color: color-mix( + in srgb, + var(--p-primary-color) 80%, + transparent + ); + justify-content: space-between; + align-items: center; + display: none; + + &__action { + color: var(--p-primary-color-text); + cursor: pointer; + padding: 0.5rem; + border-radius: 1rem; + + &:hover { + background-color: var(--p-primary-color); + } + } + + &__remove { + animation: left-to-right 0.3s ease-in; + } + &__view { + animation: right-to-left 0.3s ease-in; + } + } + + &__name { + margin: 0; + padding: 0; + color: var(--p-text-color); + } +} + +@keyframes right-to-left { + from { + transform: translateX(-100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes left-to-right { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.spec.ts new file mode 100644 index 00000000..b9f4e9f5 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UploadedFileItemComponent } from './uploaded-file-item.component'; + +describe('UploadedFileItemComponent', () => { + let component: UploadedFileItemComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UploadedFileItemComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(UploadedFileItemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.ts new file mode 100644 index 00000000..fcaab851 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.ts @@ -0,0 +1,25 @@ +import { Component, input, output } from '@angular/core'; +import { animate, style, transition, trigger } from "@angular/animations" +import { RouterLink } from '@angular/router'; + +@Component({ + selector: 'app-uploaded-file-item', + imports: [RouterLink], + templateUrl: './uploaded-file-item.component.html', + styleUrl: './uploaded-file-item.component.scss', + animations: [ + trigger('scaleAnimation', [ + transition(':enter', [ + style({ transform: 'scale(0.8)', opacity: 0 }), + animate( + '200ms ease-out', + style({ transform: 'scale(1)', opacity: 1 }) + ) + ]), + ]) + ] +}) +export class UploadedFileItemComponent { + public fileName = input(''); + public removeFile = output(); +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html index ac7c891d..18010f4d 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html @@ -41,6 +41,10 @@
}
-
+ @for(file of files; track file.name){ + + }
+ +
diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss index a01a7c48..7ab0dfb6 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss @@ -19,6 +19,7 @@ &__zone-area { block-size: 100%; + min-block-size: 50vh; flex: 1; border: 0.2rem dashed rgba(0, 0, 0, 0.2); border-radius: 0.5rem; diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts index ba6bc06b..a48c58b4 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts @@ -1,12 +1,25 @@ import { NgClass } from '@angular/common'; import { Component, ElementRef, ViewChild } from '@angular/core'; import { Button } from 'primeng/button'; +import { UploadedFileItemComponent } from './components/uploaded-file-item/uploaded-file-item.component'; +import { animate, style, transition, trigger } from "@angular/animations" +import { RouterOutlet } from '@angular/router'; @Component({ selector: 'app-file-uploader', - imports: [Button, NgClass], + imports: [Button, NgClass, UploadedFileItemComponent, RouterOutlet], templateUrl: './file-uploader.component.html', - styleUrl: './file-uploader.component.scss' + styleUrl: './file-uploader.component.scss', + animations: [ + trigger('scaleAnimation', [ + transition(':leave', [ + animate( + '150ms ease-in', + style({ transform: 'scale(0.8)', opacity: 0 }) + ) + ]) + ]) + ] }) export class FileUploaderComponent { @ViewChild('fileInput') fileInput!: ElementRef; @@ -68,8 +81,8 @@ export class FileUploaderComponent { this.files.push(...newFiles); } - removeFile(fileToRemove: File): void { - this.files = this.files.filter(file => file !== fileToRemove); + removeFile(fileName: string): void { + this.files = this.files.filter(file => file.name !== fileName); } clearFiles(): void { From f8ded5fb6408ff7ff5d58c8306da4e46be2bf1f1 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Wed, 3 Sep 2025 22:00:18 +0330 Subject: [PATCH 024/100] enhance styles in file uploader --- .../file-editor/file-editor.component.html | 1 + .../file-editor/file-editor.component.scss | 0 .../file-editor/file-editor.component.spec.ts | 23 +++++++++ .../file-editor/file-editor.component.ts | 11 +++++ .../uploaded-file-item.component.html | 4 +- .../uploaded-file-item.component.scss | 10 ++-- .../file-uploader.component.html | 1 + .../file-uploader.component.scss | 49 +++++++++---------- .../home/manage-files/manage-files.routing.ts | 9 +++- 9 files changed, 74 insertions(+), 34 deletions(-) create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.html create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.scss create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.ts diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.html new file mode 100644 index 00000000..f0ff90d3 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.html @@ -0,0 +1 @@ +

file-editor works!

diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.spec.ts new file mode 100644 index 00000000..a5c455ee --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FileEditorComponent } from './file-editor.component'; + +describe('FileEditorComponent', () => { + let component: FileEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FileEditorComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FileEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.ts new file mode 100644 index 00000000..e0927ab6 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-file-editor', + imports: [], + templateUrl: './file-editor.component.html', + styleUrl: './file-editor.component.scss' +}) +export class FileEditorComponent { + +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.html index c78c9284..0ae29098 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.html @@ -1,10 +1,10 @@
-

{{ fileName() }}

+

{{ fileName() }}

- +
diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.scss index d4ac2651..113ae9a3 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/components/uploaded-file-item/uploaded-file-item.component.scss @@ -3,14 +3,10 @@ overflow: hidden; width: fit-content; min-inline-size: 5rem; - height: 2rem; + height: 2.2rem; border-radius: 1rem; border: 0.1rem solid var(--p-primary-color); - text-align: center; - display: grid; - place-items: center; padding: 0.5rem; - cursor: pointer; transition: all 0.3s ease-in-out; &:hover &__actions { @@ -33,6 +29,7 @@ display: none; &__action { + cursor: pointer; color: var(--p-primary-color-text); cursor: pointer; padding: 0.5rem; @@ -54,6 +51,9 @@ &__name { margin: 0; padding: 0; + display: flex; + gap: 0.2rem; + align-items: center; color: var(--p-text-color); } } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html index 18010f4d..e051651f 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html @@ -40,6 +40,7 @@ Drop files here to upload
} +

Uploaded Files

@for(file of files; track file.name){ diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss index 7ab0dfb6..b0188a20 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.scss @@ -8,8 +8,16 @@ height: 100%; display: flex; flex-direction: column; - gap: 2rem; + gap: 1rem; padding: 1rem; + background-color: rgba(214, 214, 214, 0.11); + border-radius: 1rem; + box-shadow: 0 0 0.5rem 0.2rem rgba(48, 48, 48, 0.116); + margin: 1rem 0; + + h4 { + margin: 0; + } &__actions { display: flex; @@ -42,38 +50,27 @@ background-size: 200% 100%; animation: bg-shimmer 1.5s linear infinite; } - - i { - font-size: 2rem; - margin-bottom: 0.5rem; - } - - span { - font-weight: 500; - } } &__uploaded-file-list { - margin-top: 1rem; display: flex; - flex-direction: column; gap: 0.5rem; - &__item { - padding: 0.5rem 1rem; - background-color: #f1f1f1; - border-radius: 0.25rem; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.95rem; - color: #333; + // &__item { + // padding: 0.5rem 1rem; + // background-color: #f1f1f1; + // border-radius: 0.25rem; + // display: flex; + // justify-content: space-between; + // align-items: center; + // font-size: 0.95rem; + // color: #333; - .pi-times { - cursor: pointer; - color: #c00; - } - } + // .pi-times { + // cursor: pointer; + // color: #c00; + // } + // } } } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts index 237b21b2..dbb0565b 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/manage-files.routing.ts @@ -1,6 +1,7 @@ import { Routes } from "@angular/router"; import { ManageFilesComponent } from "./manage-files.component"; import { AddNewFileComponent } from "./components/add-new-file/add-new-file.component"; +import { FileEditorComponent } from "./components/add-new-file/components/file-editor/file-editor.component"; export const manageFilesRoutes: Routes = [ @@ -15,6 +16,12 @@ export const manageFilesRoutes: Routes = [ }, { path: 'new-file', - component: AddNewFileComponent + component: AddNewFileComponent, + children: [ + { + path: ':file-name', + component: FileEditorComponent + } + ] } ] \ No newline at end of file From 1687500e4df7583129793ef93d0414282cd76bd7 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Wed, 3 Sep 2025 23:26:20 +0330 Subject: [PATCH 025/100] implement store for managing uploaded files --- .../add-new-file/add-new-file.component.ts | 8 +-- .../file-uploader.component.html | 6 +- .../file-uploader/file-uploader.component.ts | 61 ++++++++----------- .../add-new-file/models/file.model.ts | 7 +++ .../upload-file-store.service.spec.ts | 16 +++++ .../upload-file/upload-file-store.service.ts | 56 +++++++++++++++++ .../upload-file-store.service.spec.ts | 16 ----- .../upload-file/upload-file-store.service.ts | 9 --- 8 files changed, 110 insertions(+), 69 deletions(-) create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/models/file.model.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.spec.ts create mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts delete mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.spec.ts delete mode 100644 etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.ts diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.ts index fc607145..8bde26fa 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/add-new-file.component.ts @@ -1,12 +1,12 @@ import { Component } from '@angular/core'; import { FileUploaderComponent } from './components/file-uploader/file-uploader.component'; +import { UploadFileStore } from './stores/upload-file/upload-file-store.service'; @Component({ selector: 'app-add-new-file', templateUrl: './add-new-file.component.html', imports: [FileUploaderComponent], - styleUrl: './add-new-file.component.scss' + providers: [UploadFileStore], + styleUrl: './add-new-file.component.scss', }) -export class AddNewFileComponent { - -} +export class AddNewFileComponent {} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html index e051651f..a9e72f40 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html @@ -28,11 +28,11 @@ (click)="clearFiles()" >
- @if(files.length === 0 || isDragging){ + @if(vm().files.length === 0 || vm().isDragging){
@@ -42,7 +42,7 @@ }

Uploaded Files

- @for(file of files; track file.name){ + @for(file of vm().files; track file.name){ }
diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts index a48c58b4..d3568f49 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts @@ -4,6 +4,7 @@ import { Button } from 'primeng/button'; import { UploadedFileItemComponent } from './components/uploaded-file-item/uploaded-file-item.component'; import { animate, style, transition, trigger } from "@angular/animations" import { RouterOutlet } from '@angular/router'; +import { UploadFileStore } from '../../stores/upload-file/upload-file-store.service'; @Component({ selector: 'app-file-uploader', @@ -16,76 +17,62 @@ import { RouterOutlet } from '@angular/router'; animate( '150ms ease-in', style({ transform: 'scale(0.8)', opacity: 0 }) - ) - ]) - ]) - ] + ), + ]), + ]), + ], }) export class FileUploaderComponent { @ViewChild('fileInput') fileInput!: ElementRef; + public readonly vm; - isDragging = false; - files: File[] = []; + constructor(private uploadFileStore: UploadFileStore) { + this.vm = this.uploadFileStore.vm; + } onDragOver(event: DragEvent): void { - // This is crucial to allow a drop. - event.preventDefault(); event.stopPropagation(); - this.isDragging = true; + event.preventDefault(); + this.uploadFileStore.setDragging(true); } onDragLeave(event: DragEvent): void { - event.preventDefault(); event.stopPropagation(); - this.isDragging = false; + event.preventDefault(); + this.uploadFileStore.setDragging(false); } onDrop(event: DragEvent): void { event.preventDefault(); event.stopPropagation(); - this.isDragging = false; + this.uploadFileStore.setDragging(false); const droppedFiles = event.dataTransfer?.files; - console.log(droppedFiles); + if (droppedFiles) { - this.handleFiles(Array.from(droppedFiles)); + const files = Array.from(droppedFiles); + files.forEach((file: File) => this.uploadFileStore.addFile(file)); } } - // --- File Input and Selection Handlers --- - - // Triggers the hidden file input - openFilePicker(): void { - this.fileInput.nativeElement.click(); - } - - // Handles files selected via the file dialog onFileSelect(event: Event): void { const element = event.target as HTMLInputElement; const selectedFiles = element.files; + if (selectedFiles) { - this.handleFiles(Array.from(selectedFiles)); + Array.from(selectedFiles).forEach((file) => + this.uploadFileStore.addFile(file) + ); } - // Clear the input value to allow selecting the same file again - element.value = ''; - } - - // --- File Management --- - private handleFiles(newFiles: File[]): void { - // You can add validation here (file type, size, etc.) - // For example, to prevent duplicates: - // const uniqueFiles = newFiles.filter(newFile => - // !this.files.some(existingFile => existingFile.name === newFile.name) - // ); - this.files.push(...newFiles); + element.value = ''; } removeFile(fileName: string): void { - this.files = this.files.filter(file => file.name !== fileName); + this.uploadFileStore.removeFile(fileName); } clearFiles(): void { - this.files = []; + this.uploadFileStore.clearFiles(); } } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/models/file.model.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/models/file.model.ts new file mode 100644 index 00000000..85110536 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/models/file.model.ts @@ -0,0 +1,7 @@ +export interface UploadFileState { + files: File[]; + isDragging: boolean; + isUploading: boolean; + error?: string | null; + selectedFile?: File; +} \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.spec.ts new file mode 100644 index 00000000..84281e59 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { UploadFileStore } from './upload-file-store.service'; + +describe('UploadFileStore', () => { + let service: UploadFileStore; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(UploadFileStore); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts new file mode 100644 index 00000000..0b991e19 --- /dev/null +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@angular/core'; +import { ComponentStore } from '@ngrx/component-store'; +import { UploadFileState } from '../../models/file.model'; + +const initialState: UploadFileState = { + files: [], + isDragging: false, + isUploading: false, + error: null, + selectedFile: undefined +}; + + +@Injectable() +export class UploadFileStore extends ComponentStore { + constructor() { + super(initialState); + } + + public readonly vm = this.selectSignal((s) => s); + + public readonly setDragging = this.updater((state, isDragging) => ({ + ...state, + isDragging, + })); + + readonly addFile = this.updater((state, file) => { + if (!file.name.toLowerCase().endsWith('.csv')) { + console.log('file is not valid'); + return { ...state, error: 'Only .csv files are allowed' }; + } + + console.log('file is valid and added'); + return { + ...state, + files: [...state.files, file], + error: null + }; + }); + + public readonly removeFile = this.updater((state, fileName) => ({ + ...state, + files: state.files.filter((f) => f.name !== fileName), + })); + + public readonly clearFiles = this.updater((state) => ({ + ...state, + files: [], + error: null + })); + + public readonly setError = this.updater((state, error) => ({ + ...state, + error, + })); +} diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.spec.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.spec.ts deleted file mode 100644 index b81c6df7..00000000 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { UploadFileStoreService } from './upload-file-store.service'; - -describe('UploadFileStoreService', () => { - let service: UploadFileStoreService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(UploadFileStoreService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.ts deleted file mode 100644 index 3c5d9712..00000000 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/upload-file/stores/upload-file/upload-file-store.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class UploadFileStoreService { - - constructor() { } -} From 62b0b80b441093e0c54ef5ca41d4202e17365667 Mon Sep 17 00:00:00 2001 From: amirbarari Date: Thu, 4 Sep 2025 00:14:55 +0330 Subject: [PATCH 026/100] handle duplicat file upload --- etl_frontend/src/app/app.component.html | 3 +- etl_frontend/src/app/app.component.ts | 6 ++-- etl_frontend/src/app/app.config.ts | 5 +-- etl_frontend/src/app/core/core.module.ts | 4 +-- .../file-uploader.component.html | 7 +++- .../file-uploader/file-uploader.component.ts | 32 +++++++++++++++++-- .../upload-file/upload-file-store.service.ts | 10 ++++-- 7 files changed, 54 insertions(+), 13 deletions(-) diff --git a/etl_frontend/src/app/app.component.html b/etl_frontend/src/app/app.component.html index e05b723d..0646490d 100644 --- a/etl_frontend/src/app/app.component.html +++ b/etl_frontend/src/app/app.component.html @@ -1,2 +1,3 @@ + - + \ No newline at end of file diff --git a/etl_frontend/src/app/app.component.ts b/etl_frontend/src/app/app.component.ts index 5214dab9..91109c4f 100644 --- a/etl_frontend/src/app/app.component.ts +++ b/etl_frontend/src/app/app.component.ts @@ -1,14 +1,16 @@ import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; import {RouterOutlet} from '@angular/router'; +import { ConfirmationService } from 'primeng/api'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; import {Toast} from 'primeng/toast'; @Component({ standalone: true, selector: 'app-root', - imports: [RouterOutlet, Toast], + imports: [RouterOutlet, Toast, ConfirmDialogModule], templateUrl: './app.component.html', styleUrl: './app.component.scss', - schemas: [CUSTOM_ELEMENTS_SCHEMA] + schemas: [] }) export class AppComponent { } diff --git a/etl_frontend/src/app/app.config.ts b/etl_frontend/src/app/app.config.ts index 3658c408..b83697d1 100644 --- a/etl_frontend/src/app/app.config.ts +++ b/etl_frontend/src/app/app.config.ts @@ -1,5 +1,5 @@ import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'; -import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; +import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { providePrimeNG } from 'primeng/config'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; @@ -11,11 +11,12 @@ import { CoreModule } from './core/core.module'; import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { errorHandlingInterceptor } from './shared/interceptors/error-handling/error-handling.interceptor'; import { credentialsInterceptor } from './shared/interceptors/credentials/credentials.interceptor'; +import { ConfirmationService } from 'primeng/api'; export const appConfig: ApplicationConfig = { providers: [ - importProvidersFrom(CoreModule), provideAnimationsAsync(), + importProvidersFrom(CoreModule), provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), providePrimeNG({ diff --git a/etl_frontend/src/app/core/core.module.ts b/etl_frontend/src/app/core/core.module.ts index addf098f..b3d80c29 100644 --- a/etl_frontend/src/app/core/core.module.ts +++ b/etl_frontend/src/app/core/core.module.ts @@ -1,13 +1,13 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; -import {MessageService} from 'primeng/api'; +import {ConfirmationService, MessageService} from 'primeng/api'; @NgModule({ declarations: [], imports: [ CommonModule, ], - providers: [MessageService,] + providers: [MessageService, ConfirmationService] }) export class CoreModule { } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html index a9e72f40..c6bf211f 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.html @@ -28,6 +28,7 @@ (click)="clearFiles()" >
+

{{ vm().error }}

@if(vm().files.length === 0 || vm().isDragging){
Uploaded Files
@for(file of vm().files; track file.name){ - + }
diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts index d3568f49..17802353 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-uploader/file-uploader.component.ts @@ -5,6 +5,7 @@ import { UploadedFileItemComponent } from './components/uploaded-file-item/uploa import { animate, style, transition, trigger } from "@angular/animations" import { RouterOutlet } from '@angular/router'; import { UploadFileStore } from '../../stores/upload-file/upload-file-store.service'; +import { ConfirmationService } from 'primeng/api'; @Component({ selector: 'app-file-uploader', @@ -26,7 +27,9 @@ export class FileUploaderComponent { @ViewChild('fileInput') fileInput!: ElementRef; public readonly vm; - constructor(private uploadFileStore: UploadFileStore) { + constructor( + private readonly uploadFileStore: UploadFileStore, + private readonly confirmationService: ConfirmationService) { this.vm = this.uploadFileStore.vm; } @@ -51,7 +54,7 @@ export class FileUploaderComponent { if (droppedFiles) { const files = Array.from(droppedFiles); - files.forEach((file: File) => this.uploadFileStore.addFile(file)); + files.forEach((file: File) => this.handleFile(file)); } } @@ -61,7 +64,7 @@ export class FileUploaderComponent { if (selectedFiles) { Array.from(selectedFiles).forEach((file) => - this.uploadFileStore.addFile(file) + this.handleFile(file) ); } @@ -75,4 +78,27 @@ export class FileUploaderComponent { clearFiles(): void { this.uploadFileStore.clearFiles(); } + + handleFile(file: File) { + const currentFiles = this.uploadFileStore.vm().files; + const exists = currentFiles.some(f => f.name === file.name); + + if (exists) { + this.showReplacFileConfirmation(file); + } else { + this.uploadFileStore.addFile(file); + } + } + + showReplacFileConfirmation(file: File): void { + this.confirmationService.confirm({ + message: `File "${file.name}" already exists. Replace it?`, + header: 'Duplicate File', + icon: 'pi pi-exclamation-triangle', + acceptLabel: 'Replace', + rejectLabel: 'Ignore', + accept: () => this.uploadFileStore.replaceFile(file), + reject: () => { } + }); + } } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts index 0b991e19..e96b47b1 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts @@ -26,11 +26,9 @@ export class UploadFileStore extends ComponentStore { readonly addFile = this.updater((state, file) => { if (!file.name.toLowerCase().endsWith('.csv')) { - console.log('file is not valid'); return { ...state, error: 'Only .csv files are allowed' }; } - console.log('file is valid and added'); return { ...state, files: [...state.files, file], @@ -38,6 +36,14 @@ export class UploadFileStore extends ComponentStore { }; }); + readonly replaceFile = this.updater((state, file) => { + const files = state.files.map(f => + f.name === file.name + ? file : f + ); + return { ...state, files }; + }); + public readonly removeFile = this.updater((state, fileName) => ({ ...state, files: state.files.filter((f) => f.name !== fileName), From 88ee2461cd3736981a751d60ada42f6ff004d70d Mon Sep 17 00:00:00 2001 From: amirbarari Date: Thu, 4 Sep 2025 00:52:25 +0330 Subject: [PATCH 027/100] implement file-editor component to edit uploaded files --- .../file-editor/file-editor.component.html | 35 ++++++++++- .../file-editor/file-editor.component.ts | 60 ++++++++++++++++++- .../upload-file/upload-file-store.service.ts | 5 ++ 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.html b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.html index f0ff90d3..6a9a1260 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.html +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.html @@ -1 +1,34 @@ -

file-editor works!

+

file name to edit

+ + + + Original Column Name + New Column Name (Editable) + Data Type + + + + + {{ col.originalHeader }} + + + + + + + + + \ No newline at end of file diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.ts index e0927ab6..0ea76b09 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/components/file-editor/file-editor.component.ts @@ -1,11 +1,65 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { TableModule } from 'primeng/table'; +import { UploadFileStore } from '../../stores/upload-file/upload-file-store.service'; +import { Select } from 'primeng/select'; + +export interface CsvColumnConfig { + originalHeader: string; + newHeader: string; + selectedType: string; +} @Component({ selector: 'app-file-editor', - imports: [], + imports: [TableModule, FormsModule, Select], templateUrl: './file-editor.component.html', styleUrl: './file-editor.component.scss' }) -export class FileEditorComponent { +export class FileEditorComponent implements OnInit { + + dbTypes = [ + { label: 'String', value: 'string' }, + { label: 'Number', value: 'number' }, + { label: 'Date', value: 'date' }, + { label: 'Boolean', value: 'boolean' } + ]; + + columnConfigurations: CsvColumnConfig[] = []; + constructor(private activatRoute: ActivatedRoute, private readonly uploadFileStore: UploadFileStore) { } + + ngOnInit(): void { + this.activatRoute.params.subscribe((params) => { + const fileName = params['file-name']; + if (fileName) { + this.getFile(fileName); + } + }); + } + + getFile(fileName: string): void { + const file = this.uploadFileStore.getFile(fileName); + if(file) this.parseCsvForPreview(file); + } + + private parseCsvForPreview(file: File): void { + const reader = new FileReader(); + + reader.onload = (e) => { + const text = reader.result as string; + const lines = text.split(/\r\n|\n/); // Split into lines + + if (lines.length > 0) { + const headers = lines[0].split(',').map(h => h.trim()); + this.columnConfigurations = headers.map(header => ({ + originalHeader: header, + newHeader: header, // Initially, newHeader is same as original + selectedType: 'TEXT' // Default to TEXT + })); + } + }; + reader.readAsText(file); + } } diff --git a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts index e96b47b1..0e8339ab 100644 --- a/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts +++ b/etl_frontend/src/app/features/dashboard/components/home/manage-files/components/add-new-file/stores/upload-file/upload-file-store.service.ts @@ -44,6 +44,11 @@ export class UploadFileStore extends ComponentStore { return { ...state, files }; }); + public getFile(fileName: string): File | undefined { + const state = this.get(); + return state.files.find(f => f.name === fileName); + } + public readonly removeFile = this.updater((state, fileName) => ({ ...state, files: state.files.filter((f) => f.name !== fileName), From 806050f1ac3d5a9e846a6809a590f8519d569aa4 Mon Sep 17 00:00:00 2001 From: Mohammad Amin Dadkhah Date: Fri, 5 Sep 2025 19:00:58 +0330 Subject: [PATCH 028/100] fix: fix style of home page and sidebar --- .../shelved.patch | 123 ---- ...heckout_at_8_28_2025_2_00_AM__Changes_.xml | 4 - .../shelved.patch | 471 --------------- ...eckout_at_8_29_2025_12_26_PM__Changes_.xml | 4 - .../shelved.patch | 546 ------------------ ...eckout_at_8_29_2025_12_38_PM__Changes_.xml | 4 - .../shelved.patch | 501 ---------------- ..._Update_at_8_29_2025_3_29_PM__Changes_.xml | 4 - .../shelved.patch | 492 ---------------- ..._Update_at_8_29_2025_4_18_PM__Changes_.xml | 4 - .../components/home/home.component.html | 2 +- .../components/home/home.component.scss | 12 +- .../manage-workflows.component.html | 3 +- 13 files changed, 9 insertions(+), 2161 deletions(-) delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_8_28_2025_2_00_AM_[Changes]/shelved.patch delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_8_28_2025_2_00_AM__Changes_.xml delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_8_29_2025_12_26_PM_[Changes]/shelved.patch delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_8_29_2025_12_26_PM__Changes_.xml delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_8_29_2025_12_38_PM_[Changes]/shelved.patch delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_8_29_2025_12_38_PM__Changes_.xml delete mode 100644 .idea/shelf/Uncommitted_changes_before_Update_at_8_29_2025_3_29_PM_[Changes]/shelved.patch delete mode 100644 .idea/shelf/Uncommitted_changes_before_Update_at_8_29_2025_3_29_PM__Changes_.xml delete mode 100644 .idea/shelf/Uncommitted_changes_before_Update_at_8_29_2025_4_18_PM_[Changes]/shelved.patch delete mode 100644 .idea/shelf/Uncommitted_changes_before_Update_at_8_29_2025_4_18_PM__Changes_.xml diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_8_28_2025_2_00_AM_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_8_28_2025_2_00_AM_[Changes]/shelved.patch deleted file mode 100644 index 861fdb59..00000000 --- a/.idea/shelf/Uncommitted_changes_before_Checkout_at_8_28_2025_2_00_AM_[Changes]/shelved.patch +++ /dev/null @@ -1,123 +0,0 @@ -Index: .idea/workspace.xml -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP -<+>\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {\r\n "lastFilter": {\r\n "state": "OPEN",\r\n "assignee": "MohammadAminDadkhah"\r\n }\r\n}\r\n {\r\n "selectedUrlAndAccountId": {\r\n "url": "git@github.com:Star-Academy/Summer1404-Project-Team03.git",\r\n "accountId": "fc338c9a-1671-4db6-9bac-ecf9ac67d7e8"\r\n },\r\n "recentNewPullRequestHead": {\r\n "server": {\r\n "useHttp": false,\r\n "host": "github.com",\r\n "port": null,\r\n "suffix": null\r\n },\r\n "owner": "Star-Academy",\r\n "repository": "Summer1404-Project-Team03"\r\n }\r\n}\r\n \r\n \r\n {\r\n "customColor": "",\r\n "associatedIndex": 2\r\n}\r\n \r\n \r\n \r\n \r\n \r\n \r\n {\r\n "keyToString": {\r\n "DefaultHtmlFileTemplate": "HTML File",\r\n "RunOnceActivity.ShowReadmeOnStart": "true",\r\n "RunOnceActivity.git.unshallow": "true",\r\n "git-widget-placeholder": "#10 on frontend/profile",\r\n "last_opened_file_path": "C:/Users/Mohap/Desktop/ETL-Project/Summer1404-Project-Team03/etl_frontend/src/app/features/dashboard/components/profile/modals/shared",\r\n "node.js.detected.package.eslint": "true",\r\n "node.js.detected.package.tslint": "true",\r\n "node.js.selected.package.eslint": "(autodetect)",\r\n "node.js.selected.package.tslint": "(autodetect)",\r\n "nodejs_package_manager_path": "npm",\r\n "settings.editor.selected.configurable": "preferences.pluginManager",\r\n "ts.external.directory.path": "C:\\\\Program Files\\\\JetBrains\\\\WebStorm 2024.3.5\\\\plugins\\\\javascript-plugin\\\\jsLanguageServicesImpl\\\\external",\r\n "vue.rearranger.settings.migration": "true"\r\n }\r\n}\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n 1755439563544\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n -Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP -<+>UTF-8 -=================================================================== -diff --git a/.idea/workspace.xml b/.idea/workspace.xml ---- a/.idea/workspace.xml (revision b69baac63be2630109349b327632b069aecbb91b) -+++ b/.idea/workspace.xml (date 1756333239157) -@@ -4,48 +4,8 @@ -