diff --git a/backend/src/main/java/com/github/bytestrick/tabula/config/SecurityConfig.java b/backend/src/main/java/com/github/bytestrick/tabula/config/SecurityConfig.java index 2e9e44f5..ff90e6d9 100644 --- a/backend/src/main/java/com/github/bytestrick/tabula/config/SecurityConfig.java +++ b/backend/src/main/java/com/github/bytestrick/tabula/config/SecurityConfig.java @@ -43,7 +43,7 @@ CorsConfigurationSource corsConfigurationSource() { configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/auth/**", configuration); + source.registerCorsConfiguration("/**", configuration); return source; } diff --git a/backend/src/main/java/com/github/bytestrick/tabula/controller/TableController.java b/backend/src/main/java/com/github/bytestrick/tabula/controller/TableController.java index 777779cf..a996df2a 100644 --- a/backend/src/main/java/com/github/bytestrick/tabula/controller/TableController.java +++ b/backend/src/main/java/com/github/bytestrick/tabula/controller/TableController.java @@ -25,7 +25,6 @@ public class TableController { @GetMapping("after/{tableId}") public ResponseEntity> getTablesAfter(@PathVariable UUID tableId, @RequestParam int quantity) { - return ResponseEntity.ok().body(tableService.getNextTables(tableId, quantity)); } @@ -45,7 +44,6 @@ public ResponseEntity createTable(@RequestBody TableCreateDTO t @PutMapping("/{tableId}") public ResponseEntity updateTableCard(@PathVariable UUID tableId, @Valid @RequestBody TablePutDTO tablePutDTO) { - return ResponseEntity.ok().body(new InformativeResponse(tableService.updateTable(tableId, tablePutDTO))); } diff --git a/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TableCreateDTO.java b/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TableCreateDTO.java index 6a2dd09e..46c86ff9 100644 --- a/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TableCreateDTO.java +++ b/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TableCreateDTO.java @@ -9,7 +9,5 @@ public record TableCreateDTO( @NotNull @Size(min = 1, max = 50) String title, @Size(max = 500) String description, - @NotNull @PastOrPresent LocalDateTime creationDate, - LocalDateTime lastEditDate -) { -} + @NotNull @PastOrPresent LocalDateTime creationDate +) {} diff --git a/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TableCreatedDTO.java b/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TableCreatedDTO.java index d13afbaf..03825904 100644 --- a/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TableCreatedDTO.java +++ b/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TableCreatedDTO.java @@ -1,5 +1,8 @@ package com.github.bytestrick.tabula.controller.dto.table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PastOrPresent; + import java.time.LocalDateTime; import java.util.UUID; diff --git a/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TablePutDTO.java b/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TablePutDTO.java index 14193cf1..60f816d4 100644 --- a/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TablePutDTO.java +++ b/backend/src/main/java/com/github/bytestrick/tabula/controller/dto/table/TablePutDTO.java @@ -1,5 +1,7 @@ package com.github.bytestrick.tabula.controller.dto.table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PastOrPresent; import jakarta.validation.constraints.Size; import java.time.LocalDateTime; @@ -8,6 +10,6 @@ public record TablePutDTO( @Size(min = 1, max = 50) String title, @Size(max = 500) String description, - LocalDateTime lastEditTime + @NotNull @PastOrPresent LocalDateTime lastEditDate ) { } diff --git a/backend/src/main/java/com/github/bytestrick/tabula/service/TableService.java b/backend/src/main/java/com/github/bytestrick/tabula/service/TableService.java index d3039a1a..5c691f07 100644 --- a/backend/src/main/java/com/github/bytestrick/tabula/service/TableService.java +++ b/backend/src/main/java/com/github/bytestrick/tabula/service/TableService.java @@ -90,7 +90,7 @@ public TableCreatedDTO createNewTable(TableCreateDTO tableCreateDTO) { .title(tableCreateDTO.title()) .description(tableCreateDTO.description()) .creationDate(tableCreateDTO.creationDate()) - .lastEditDate(tableCreateDTO.lastEditDate()) + .lastEditDate(tableCreateDTO.creationDate()) // la data di modifica combacia con quella di creazione .userId(getAuthUser().getId()) .build(); @@ -109,7 +109,7 @@ public String updateTable(UUID tableId, TablePutDTO tablePutDTO) { .id(tableId) .title(tablePutDTO.title()) .description(tablePutDTO.description()) - .lastEditDate(tablePutDTO.lastEditTime()) + .lastEditDate(tablePutDTO.lastEditDate()) .build(); tableDAO.update(table); diff --git a/database/schema.sql b/database/schema.sql index 416f8157..7b615f56 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -35,7 +35,8 @@ CREATE TABLE data_type INSERT INTO data_type (name) VALUES ('Textual'), ('Numeric'), - ('Monetary'); + ('Monetary'), + ('Map'); CREATE TABLE tbl_table ( diff --git a/frontend/angular.json b/frontend/angular.json index 5cb8dc60..230ea72c 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -31,15 +31,22 @@ { "glob": "**/*", "input": "public" + }, + { + "glob": "*.*", + "input": "node_modules/leaflet/dist/images", + "output": "media" } ], "styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", "node_modules/bootstrap-icons/font/bootstrap-icons.min.css", - "src/styles.css" + "src/styles.css", + "node_modules/leaflet/dist/leaflet.css" ], "scripts": [ - "node_modules/bootstrap/dist/js/bootstrap.min.js" + "node_modules/bootstrap/dist/js/bootstrap.min.js", + "node_modules/leaflet/dist/leaflet.js" ] }, "configurations": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index afa1bb85..8284be59 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "^19.2.7", "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", + "leaflet": "^1.9.4", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -30,6 +31,7 @@ "@angular/compiler-cli": "^19.2.7", "@types/bootstrap": "^5.2.10", "@types/jasmine": "~5.1.0", + "@types/leaflet": "^1.9.17", "jasmine-core": "~5.2.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -5532,6 +5534,13 @@ "@types/send": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -5563,6 +5572,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/leaflet": { + "version": "1.9.17", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.17.tgz", + "integrity": "sha512-IJ4K6t7I3Fh5qXbQ1uwL3CFVbCi6haW9+53oLWgdKlLP7EaS21byWFJxxqOx9y8I0AP0actXSJLVMbyvxhkUTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -9832,6 +9851,12 @@ "shell-quote": "^1.8.1" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/less": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index db518228..17bcfb75 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,6 +22,7 @@ "@angular/router": "^19.2.7", "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", + "leaflet": "^1.9.4", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -32,6 +33,7 @@ "@angular/compiler-cli": "^19.2.7", "@types/bootstrap": "^5.2.10", "@types/jasmine": "~5.1.0", + "@types/leaflet": "^1.9.17", "jasmine-core": "~5.2.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index b896f611..7afcb444 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1,4 +1,3 @@ - diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 467ec819..0fc7cb65 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -17,27 +17,11 @@ import {ConfirmDialogComponent} from './confirm-dialog/confirm-dialog.component' export class AppComponent implements OnInit { private popUpManager: PopUpManagerService = inject(PopUpManagerService); - _ = inject(ThemeService); - @ViewChild('popUpContainer', { read: ViewContainerRef, static: true }) popUpContainer!: ViewContainerRef; ngOnInit() { - AppComponent.updateColorScheme(); this.popUpManager.setPopUpContainer(this.popUpContainer); } - - - /** - * Sync the app color scheme with the OS color scheme - */ - private static updateColorScheme() { - const darkColorSchemeQuery = matchMedia('(prefers-color-scheme: dark)'); - const onChange = (isDark: boolean) => { - document.documentElement.setAttribute('data-bs-theme', isDark ? 'dark' : 'light'); - }; - darkColorSchemeQuery.addEventListener('change', e => onChange(e.matches)); - onChange(darkColorSchemeQuery.matches); // at first load - } } diff --git a/frontend/src/app/home/home.service.ts b/frontend/src/app/home/home.service.ts index a2c4d482..ac4f2ee8 100644 --- a/frontend/src/app/home/home.service.ts +++ b/frontend/src/app/home/home.service.ts @@ -8,9 +8,7 @@ import {TableCard} from './table-card/table-card.interface'; providedIn: 'root' }) export class HomeService { - private readonly BASE_URL: string = '/tables'; - private http = inject(HttpClient); diff --git a/frontend/src/app/home/table-card/table-card.component.css b/frontend/src/app/home/table-card/table-card.component.css deleted file mode 100644 index bb1809da..00000000 --- a/frontend/src/app/home/table-card/table-card.component.css +++ /dev/null @@ -1,3 +0,0 @@ -.card:hover { - border-color: var(--selected-text-color); -} diff --git a/frontend/src/app/home/table-card/table-card.component.html b/frontend/src/app/home/table-card/table-card.component.html index 6c1d25a5..210508a9 100644 --- a/frontend/src/app/home/table-card/table-card.component.html +++ b/frontend/src/app/home/table-card/table-card.component.html @@ -1,5 +1,5 @@
-
{{ title }}
diff --git a/frontend/src/app/home/table-card/table-card.component.ts b/frontend/src/app/home/table-card/table-card.component.ts index 5670cde8..a0c7c8a3 100644 --- a/frontend/src/app/home/table-card/table-card.component.ts +++ b/frontend/src/app/home/table-card/table-card.component.ts @@ -12,14 +12,20 @@ import {TableCard} from './table-card.interface'; import {Subscription} from 'rxjs'; import {DatePipe} from '@angular/common'; import {PrettyDatePipe} from '../pretty-date.pipe'; -import {Router, RouterOutlet} from '@angular/router'; +import {Router} from '@angular/router'; import {ToastService} from '../../toast/toast.service'; import {ConfirmDialogService} from '../../confirm-dialog/confirm-dialog.service'; +import {NavbarComponent} from '../../navbar/navbar.component'; @Component({ selector: 'tbl-table-card', imports: [DatePipe, PrettyDatePipe], templateUrl: './table-card.component.html', + styles: ` + .card:active { + border-color: var(--bs-btn-active-border-color) + } + ` }) export class TableCardComponent implements OnDestroy { @Output('editTableCard') editTableCard: EventEmitter = new EventEmitter; @@ -142,5 +148,6 @@ export class TableCardComponent implements OnDestroy { onOpenCard(): void { this.router.navigate(['/tables', this.id]) .catch(err => console.error(err)); + NavbarComponent.setTableTitle(this.title); } } diff --git a/frontend/src/app/home/table-card/table-card.interface.ts b/frontend/src/app/home/table-card/table-card.interface.ts index eba8dbd9..8e91dc4b 100644 --- a/frontend/src/app/home/table-card/table-card.interface.ts +++ b/frontend/src/app/home/table-card/table-card.interface.ts @@ -4,5 +4,4 @@ export interface TableCard { description: string; creationDate?: Date; lastEditDate?: Date; - tableId?: string; } diff --git a/frontend/src/app/model/data-types/concrete-data-type/map-data-type.ts b/frontend/src/app/model/data-types/concrete-data-type/map-data-type.ts new file mode 100644 index 00000000..279eec53 --- /dev/null +++ b/frontend/src/app/model/data-types/concrete-data-type/map-data-type.ts @@ -0,0 +1,34 @@ +import {Type} from '@angular/core'; +import {BaseInputComponent} from '../../../table-components/input-components/base-input-component'; +import {BaseCellComponent} from '../../../table-components/table/cells/base-cell-component'; +import {IDataType} from '../i-data-type'; +import {DataTypeRegistryService} from '../../../services/data-type-registry.service'; +import {MapInputComponent} from '../../../table-components/input-components/map-input/map-input.component'; +import {MapCellComponent} from '../../../table-components/table/cells/map-cell/map-cell.component'; + + +export class MapDataType implements IDataType { + getDataTypeId(): number { + return DataTypeRegistryService.MAP_ID; + } + + getInputComponent(): Type { + return MapInputComponent; + } + + getNewDataType(): IDataType { + return new MapDataType(); + } + + getCellComponent(): Type { + return MapCellComponent; + } + + getIconName(): string { + return 'bi-geo-alt'; + } + + getDataTypeName(): string { + return 'Map'; + } +} diff --git a/frontend/src/app/model/pop-up-content.ts b/frontend/src/app/model/pop-up-content.ts index aaf35293..f3a9af44 100644 --- a/frontend/src/app/model/pop-up-content.ts +++ b/frontend/src/app/model/pop-up-content.ts @@ -1,12 +1,20 @@ +import {PopUp} from '../table-components/pop-up-component/pop-up.component'; + /** * Contract that external content components must implement to be used within the PopUp component. * Defines lifecycle callbacks for showing and hiding events. */ export interface PopUpContent { + /** + * Parent popup reference. + */ + popUpRef: PopUp | undefined; + /** * Lifecycle callback invoked when the pop-up becomes visible. */ onShowUp(): void; + /** * Lifecycle callback invoked when the pop-up is hidden. * @param action - Identifier of the user action that triggered hiding: diff --git a/frontend/src/app/model/table/cell.ts b/frontend/src/app/model/table/cell.ts index ec0c6ac4..be6ce047 100644 --- a/frontend/src/app/model/table/cell.ts +++ b/frontend/src/app/model/table/cell.ts @@ -12,7 +12,7 @@ export class Cell { /** The data type logic that drives validation, formatting, and editing UI. */ protected _cellDataType: IDataType; /** The current string value of the cell; `null` represents a blank cell. */ - protected _value: string | null = null; + protected _value: string = ''; /** * Constructs a new Cell instance. @@ -20,9 +20,9 @@ export class Cell { * @param cellDataType The IDataType instance defining how this cell’s data is handled. * @param value The initial value for the cell; may be any type, but stored as string. */ - constructor(cellDataType: IDataType, value: any) { + constructor(cellDataType: IDataType, value: string = '') { this._cellDataType = cellDataType; - this._value = value; + this.value = value; } /** @@ -64,18 +64,18 @@ export class Cell { /** * Gets the current value of the cell. * - * @returns The cell’s string value, or `null` if blank. + * @returns The cell’s string value, or `''` if blank. */ - get value(): string | null { + get value(): string { return this._value; } /** * Updates the cell’s value and notifies its component (if mounted). * - * @param value The new value to set; `null` clears the cell. + * @param value The new value to set; `''` clears the cell. */ - set value(value: string | null) { + set value(value: string) { this._value = value; // If the cell component is rendered, update its displayed value this.cellRef?.instance.setValue(value); diff --git a/frontend/src/app/model/table/row.ts b/frontend/src/app/model/table/row.ts index 7d078799..08fb4f51 100644 --- a/frontend/src/app/model/table/row.ts +++ b/frontend/src/app/model/table/row.ts @@ -13,7 +13,7 @@ export class Row { } - public appendNewCell(newDataType: IDataType, value: any = null): void { + public appendNewCell(newDataType: IDataType, value: string = ''): void { this.row.push(new Cell(newDataType, value)); } @@ -23,7 +23,7 @@ export class Row { } - insertNewCellAt(index: number, newDataType: IDataType, value: any = null): void { + insertNewCellAt(index: number, newDataType: IDataType, value: string = ''): void { this.row.splice(index, 0, new Cell(newDataType, value)); } diff --git a/frontend/src/app/model/table/selection.ts b/frontend/src/app/model/table/selection.ts index 0cb7e1eb..30f71ef6 100644 --- a/frontend/src/app/model/table/selection.ts +++ b/frontend/src/app/model/table/selection.ts @@ -1,32 +1,65 @@ +/** + * Class that manages a collection of selected items identified by strings. + */ export class Selection { - + /** + * A Set of currently selected IDs. + */ private selectedIds: Set = new Set(); - - selectOrUpdate(id: string): void { + /** + * Adds the specified ID to the selection. + * If the ID is already present, this method has no effect. + * + * @param id The identifier to select or update. + */ + select(id: string): void { this.selectedIds.add(id); } - + /** + * Removes the specified ID from the selection. + * If the ID is not present in the Set, this method has no effect. + * + * @param id The identifier to deselect. + */ deselect(id: string): void { this.selectedIds.delete(id); } - + /** + * Checks whether a given ID is currently selected. + * + * @param id The identifier to check. + * @returns {@code true} if the ID is in the Set of selected IDs, {@code false} otherwise. + */ isSelected(id: string): boolean { - if (this.selectedIds.has(id)) - return true; - - return false; + return this.selectedIds.has(id); } - + /** + * Returns the total number of items currently selected. + * + * @returns The count of selected IDs. + */ getSelectionNumber(): number { - return this.selectedIds.size + return this.selectedIds.size; } - + /** + * Returns an array containing all currently selected IDs. + * The array is generated from the keys of the internal Set. + * + * @returns An array of strings representing the selected IDs. + */ getSelectedIds(): string[] { return Array.from(this.selectedIds.keys()); } + + /** + * Clears the entire selection, removing all IDs from the internal Set. + */ + deselectAll(): void { + this.selectedIds.clear(); + } } diff --git a/frontend/src/app/navbar/navbar.component.html b/frontend/src/app/navbar/navbar.component.html index a2754068..7cf4b95c 100644 --- a/frontend/src/app/navbar/navbar.component.html +++ b/frontend/src/app/navbar/navbar.component.html @@ -1,7 +1,8 @@