order added to flows
This commit is contained in:
parent
958d3210c5
commit
76e7806d3d
|
|
@ -1,6 +1,6 @@
|
|||
<aside class="h-full flex flex-row
|
||||
bg-white border-r border-gray-200
|
||||
transition-all duration-300 p-1" [class.w-72]="!collapsed()" [class.w-14]="collapsed()">
|
||||
transition-all duration-300 p-1" [class.w-82]="!collapsed()" [class.w-14]="collapsed()">
|
||||
|
||||
<div class="flex flex-col h-full">
|
||||
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
<!-- CONTENT -->
|
||||
<div class="h-full bg-white flex-1">
|
||||
|
||||
<div class="space-y-6 felx-1">
|
||||
<div class="space-y-6 h-full">
|
||||
@switch (open) {
|
||||
@case ('flows') {
|
||||
<app-group-holder title="Flows" icon="bi-lightning-charge-fill">
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ export class FlowsCallServiceFake extends FlowsCallServiceBase {
|
|||
authorizationService = inject(Authorization);
|
||||
|
||||
private data: Record<string, Flow> = {
|
||||
'1': { id: '1', name: 'Test Flow 1', data: new FlowData(), visibility: 'public', author: 'Alice', createdAt: new Date(), updatedAt: new Date() },
|
||||
'2': { id: '2', name: 'Test Flow 2', data: new FlowData(), visibility: 'private', author: 'Bob', createdAt: new Date(), updatedAt: new Date() },
|
||||
'1': { id: '1', name: 'A Flow', data: new FlowData(), visibility: 'public', author: 'Alice', createdAt: new Date("December 17, 2023 03:24:00"), updatedAt: new Date("January 7, 2026 12:24:00") },
|
||||
'2': { id: '2', name: 'Test Flow', data: new FlowData(), visibility: 'private', author: 'Bob', createdAt: new Date("April 25, 2025 12:24:00"), updatedAt: new Date("April 27, 2025 18:42:00") },
|
||||
};
|
||||
|
||||
override retrieveAllFlows() {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<div class="group border
|
||||
px-3 py-2 cursor-pointer
|
||||
px-3 py-2 cursor-pointer w-full
|
||||
transition
|
||||
hover:shadow-[0_0_12px_rgba(99,102,241,0.6)]
|
||||
bg-indigo-50 border-indigo-200 select-none" (click)="toggleDetails()" (dblclick)="open()">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<div class="flex items-center justify-between text-sm ">
|
||||
<div class="flex items-center gap-2 text-indigo-700 font-medium">
|
||||
<i class="bi" [ngClass]="{
|
||||
'bi-globe': flow().visibility === 'public',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
@if (loading()) {
|
||||
<div class="flex items-center justify-center h-32">
|
||||
<div class="spinner-border text-indigo-500" role="status">
|
||||
|
|
@ -6,36 +5,54 @@
|
|||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="h-screen flex flex-col">
|
||||
<div class="btn-group w-full mb-3" role="group">
|
||||
|
||||
<button type="button" class="btn btn-sm w-full transition" [class.btn-primary]="filter() === 'all'"
|
||||
[class.btn-outline-primary]="filter() !== 'all'" (click)="filter.set('all')">
|
||||
All
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-sm w-full transition" [class.btn-primary]="filter() === 'public'"
|
||||
[class.btn-outline-primary]="filter() !== 'public'" (click)="filter.set('public')">
|
||||
Public
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-sm w-full transition" [class.btn-primary]="filter() === 'private'"
|
||||
[class.btn-outline-primary]="filter() !== 'private'" (click)="filter.set('private')">
|
||||
Private
|
||||
</button>
|
||||
<div class="h-screen flex flex-col">
|
||||
<div class="w-full max-w-sm py-2">
|
||||
<div class="relative flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
||||
class="absolute w-5 h-5 top-2.5 left-2.5 text-slate-600">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10.5 3.75a6.75 6.75 0 1 0 0 13.5 6.75 6.75 0 0 0 0-13.5ZM2.25 10.5a8.25 8.25 0 1 1 14.59 5.28l4.69 4.69a.75.75 0 1 1-1.06 1.06l-4.69-4.69A8.25 8.25 0 0 1 2.25 10.5Z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
||||
<input
|
||||
class="w-full bg-transparent placeholder:text-slate-400 text-slate-700 text-sm border border-slate-200 rounded-md pl-10 pr-3 py-2 transition duration-300 ease focus:outline-none focus:border-slate-400 hover:border-slate-300 shadow-sm focus:shadow"
|
||||
placeholder="Search flows..." [(ngModel)]="searchTerm"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group w-full mb-3" role="group">
|
||||
|
||||
@if (filteredFlows().length === 0) {
|
||||
<div class="text-gray-400 text-center mt-10 flex flex-col flex-1 justify-center">
|
||||
No flows found
|
||||
</div>
|
||||
} @else {
|
||||
<div class="flex-1 overflow-y-auto h-full py-2 gap-2 flex flex-col items-center max-h-[calc(100vh-16rem)]">
|
||||
@for (flow of filteredFlows(); track flow.id) {
|
||||
<app-flow-item [(detailOpenedId)]="detailOpenedId" [flow]="flow"></app-flow-item>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm w-full transition" [class.btn-primary]="filter() === 'all'"
|
||||
[class.btn-outline-primary]="filter() !== 'all'" (click)="filter.set('all')">
|
||||
All
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-sm w-full transition" [class.btn-primary]="filter() === 'public'"
|
||||
[class.btn-outline-primary]="filter() !== 'public'" (click)="filter.set('public')">
|
||||
Public
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-sm w-full transition" [class.btn-primary]="filter() === 'private'"
|
||||
[class.btn-outline-primary]="filter() !== 'private'" (click)="filter.set('private')">
|
||||
Private
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex pt-2 pb-1 text-sm w-full items-center text-center justify-center">
|
||||
<app-ordering [orderFields]="orderFields" (orderChanged)="onOrderChanged($event)"></app-ordering>
|
||||
</div>
|
||||
@if (filteredFlows().length === 0) {
|
||||
<div class="text-gray-400 text-center mt-10 flex flex-col flex-1 pt-10">
|
||||
No flows found
|
||||
</div>
|
||||
} @else {
|
||||
<div class="flex-1 overflow-y-auto h-full w-full py-2 gap-2 flex flex-col items-stretch max-h-[calc(100vh-32rem)]">
|
||||
@for (flow of filteredFlows(); track flow.id) {
|
||||
<app-flow-item [(detailOpenedId)]="detailOpenedId" [flow]="flow"></app-flow-item>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
@ -1,26 +1,35 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, inject, signal, Signal, WritableSignal } from '@angular/core';
|
||||
import { Component, computed, inject, model, output, signal, Signal, WritableSignal } from '@angular/core';
|
||||
import { Flow } from '@models/flow';
|
||||
import { FlowsService } from '@services/flows/flows';
|
||||
import { FlowItem } from './flow-item/flow-item';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { OrderEvent, OrderField, Ordering } from "@shared/ordering/ordering";
|
||||
|
||||
type FlowFilter = 'all' | 'public' | 'private';
|
||||
|
||||
@Component({
|
||||
selector: 'app-flows-list',
|
||||
imports: [FlowItem],
|
||||
imports: [FlowItem, FormsModule, Ordering],
|
||||
templateUrl: './flows-list.html',
|
||||
styleUrl: './flows-list.css',
|
||||
})
|
||||
export class FlowsList {
|
||||
filter = signal<FlowFilter>('all');
|
||||
|
||||
orderFields : OrderField[] = [
|
||||
{ field: 'name', label: 'Name' },
|
||||
{ field: 'updatedAt', label: 'Last Update Date' },
|
||||
{ field: 'createdAt', label: 'Creation Date' }
|
||||
];
|
||||
|
||||
searchTerm = model<string>('');
|
||||
|
||||
private flowsService = inject(FlowsService);
|
||||
|
||||
loading: WritableSignal<boolean> = signal(true);
|
||||
|
||||
flows: Signal<Flow[]> | undefined;
|
||||
flows: Signal<Flow[] | null> = signal(null);
|
||||
|
||||
detailOpenedId = signal<string | null>(null);
|
||||
|
||||
|
|
@ -32,11 +41,24 @@ export class FlowsList {
|
|||
}
|
||||
|
||||
filteredFlows = computed(() => {
|
||||
const flows = this.flows?.();
|
||||
console.log('Filtering flows', flows?.map(f => f.name));
|
||||
const flows = this.flows();
|
||||
if (!flows) return [];
|
||||
if (this.filter() === 'all') return flows;
|
||||
return flows.filter(f => f.visibility === this.filter());
|
||||
const filteredFlows = flows.filter(f => f.name.toLowerCase().includes(this.searchTerm().toLowerCase()));
|
||||
if (this.filter() === 'all') return filteredFlows;
|
||||
return filteredFlows.filter(f => f.visibility === this.filter());
|
||||
});
|
||||
|
||||
onOrderChanged(event: OrderEvent) {
|
||||
const { orderBy, orderDir } = event;
|
||||
const flows = this.filteredFlows();
|
||||
if (!orderBy) return flows;
|
||||
return flows.sort((a, b) => {
|
||||
const aValue = (a as any)[orderBy];
|
||||
const bValue = (b as any)[orderBy];
|
||||
if (aValue < bValue) return orderDir === 'asc' ? -1 : 1;
|
||||
if (aValue > bValue) return orderDir === 'asc' ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<div class="flex items-center w-full justify-between gap-2">
|
||||
<div class="form-floating">
|
||||
<select id="orderSelect"
|
||||
class="form-select form-select-sm text-sm w-auto"
|
||||
[(ngModel)]="orderBy"
|
||||
placeholder="Order By"
|
||||
>
|
||||
|
||||
@for (opt of orderFields(); track opt.field) {
|
||||
<option [ngValue]="opt.field">
|
||||
{{ opt.label || opt.field }}
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
<label for="orderSelect">Order by</label>
|
||||
</div>
|
||||
<!-- direzione -->
|
||||
<div class="btn-group">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm px-1 w-6 h-14 flex items-center justify-center"
|
||||
[class.btn-primary]="orderDir() === 'asc'"
|
||||
[class.btn-outline-primary]="orderDir() !== 'asc'"
|
||||
(click)="orderDir.set('asc')"
|
||||
title="Ascending"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm px-1 w-6 h-14 flex items-center justify-center"
|
||||
[class.btn-primary]="orderDir() === 'desc'"
|
||||
[class.btn-outline-primary]="orderDir() !== 'desc'"
|
||||
(click)="orderDir.set('desc')"
|
||||
title="Descending"
|
||||
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Ordering } from './ordering';
|
||||
|
||||
describe('Ordering', () => {
|
||||
let component: Ordering;
|
||||
let fixture: ComponentFixture<Ordering>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Ordering]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Ordering);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { Component, effect, input, model, output } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
|
||||
export type OrderEvent = { orderBy: string | null; orderDir: orderDirType };
|
||||
export type orderDirType = 'asc' | 'desc';
|
||||
export type OrderField = { field: string; label?: string };
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-ordering',
|
||||
imports: [FormsModule],
|
||||
templateUrl: './ordering.html',
|
||||
styleUrl: './ordering.css',
|
||||
})
|
||||
export class Ordering {
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
this.orderChanged.emit({ orderBy: this.orderBy(), orderDir: this.orderDir() });
|
||||
});
|
||||
}
|
||||
|
||||
orderFields = input.required<OrderField[]>();
|
||||
|
||||
orderDir = model<orderDirType>('asc');
|
||||
|
||||
orderBy = model<string | null>(null);
|
||||
|
||||
orderChanged = output<OrderEvent>();
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue