editCommercialProps, confirmationService, MessageService, Drag & Drop
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { AngularCropperjsModule } from 'angular-cropperjs';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
@@ -19,7 +18,7 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||
@Component({
|
||||
selector: 'app-account',
|
||||
standalone: true,
|
||||
imports: [SharedModule, AngularCropperjsModule],
|
||||
imports: [SharedModule],
|
||||
providers: [],
|
||||
templateUrl: './account.component.html',
|
||||
styleUrl: './account.component.scss',
|
||||
|
||||
@@ -7,8 +7,6 @@ import { createDefaultBusinessListing, map2User, routeListingWithState } from '.
|
||||
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { AngularCropperjsModule } from 'angular-cropperjs';
|
||||
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { QuillModule } from 'ngx-quill';
|
||||
|
||||
@@ -27,7 +25,7 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||
@Component({
|
||||
selector: 'business-listing',
|
||||
standalone: true,
|
||||
imports: [SharedModule, ArrayToStringPipe, AngularCropperjsModule, DragDropModule, MixedCdkDragDropModule, QuillModule, NgxCurrencyDirective, NgSelectModule],
|
||||
imports: [SharedModule, ArrayToStringPipe, DragDropModule, QuillModule, NgxCurrencyDirective, NgSelectModule],
|
||||
providers: [],
|
||||
templateUrl: './edit-business-listing.component.html',
|
||||
styleUrl: './edit-business-listing.component.scss',
|
||||
|
||||
@@ -146,10 +146,11 @@
|
||||
<div class="container mx-auto p-4">
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<h1 class="text-2xl font-semibold mb-6">Edit Listing</h1>
|
||||
@if (listing){
|
||||
<form #listingForm="ngForm">
|
||||
<div class="mb-4">
|
||||
<label for="listingsCategory" class="block text-sm font-bold text-gray-700 mb-1">Listing category</label>
|
||||
<select id="listingsCategory" [(ngModel)]="listing.listingsCategory" name="listingsCategory" class="w-full p-2 border border-gray-300 rounded-md">
|
||||
<select id="listingsCategory" [ngModel]="listingsCategory" name="listingsCategory" class="w-full p-2 border border-gray-300 rounded-md">
|
||||
<option value="business">Business</option>
|
||||
<option value="commercialProperty">Commercial Property</option>
|
||||
</select>
|
||||
@@ -220,11 +221,10 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container mx-auto p-4">
|
||||
<div cdkDropList class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" (cdkDropListDropped)="drop($event)">
|
||||
<!-- <div class="container mx-auto p-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
@for (image of listing.imageOrder; track listing.imageOrder) {
|
||||
<div cdkDrag class="relative aspect-video cursor-move">
|
||||
<!-- <img [src]="image" class="w-full h-full object-cover rounded-lg shadow-md" /> -->
|
||||
<div class="relative aspect-video cursor-move">
|
||||
<img src="{{ env.imageBaseUrl }}/pictures/property/{{ listing.imagePath }}/{{ listing.serialId }}/{{ image }}?_ts={{ ts }}" [alt]="image" class="w-full h-full object-cover rounded-lg shadow-md" />
|
||||
<div class="absolute top-2 right-2 bg-white rounded-full p-1 shadow-md">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="w-4 h-4 text-gray-600">
|
||||
@@ -234,12 +234,56 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="container mx-auto p-4">
|
||||
<!-- <div class="grid-container"> -->
|
||||
<!-- @for (image of listing.imageOrder; track image) {
|
||||
<div cdkDrag class="grid-item">
|
||||
<div class="image-box">
|
||||
<img [src]="getImageUrl(image)" [alt]="image" class="w-full h-full object-cover rounded-lg shadow-md" />
|
||||
<div class="absolute top-2 right-2 bg-white rounded-full p-1 shadow-md">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="w-4 h-4 text-gray-600">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} -->
|
||||
<app-drag-drop-mixed [listing]="listing" (imageOrderChanged)="imageOrderChanged($event)" (imageToDelete)="deleteConfirm($event)"></app-drag-drop-mixed>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
<div class="bg-white p-4 rounded-lg shadow">
|
||||
<h2 class="text-lg font-semibold mb-2">Property Pictures</h2>
|
||||
<p class="text-sm text-gray-500 mb-4">(Pictures can be uploaded once the listing is posted initially)</p>
|
||||
<button
|
||||
(click)="openFileDialog()"
|
||||
class="flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
<svg class="mr-2 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||
</svg>
|
||||
Upload
|
||||
</button>
|
||||
<input type="file" #fileInput style="display: none" (change)="fileChangeEvent($event)" accept="image/*" />
|
||||
</div>
|
||||
@if (mode==='create'){
|
||||
<button (click)="save()" class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600">Post Listing</button>
|
||||
<button (click)="save()" class="bg-blue-500 text-white px-4 py-2 mt-3 rounded-md hover:bg-blue-600">Post Listing</button>
|
||||
} @else {
|
||||
<button (click)="save()" class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600">Update Listing</button>
|
||||
<button (click)="save()" class="bg-blue-500 text-white px-4 py-2 mt-3 rounded-md hover:bg-blue-600">Update Listing</button>
|
||||
}
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Modal -->
|
||||
<div *ngIf="showModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center">
|
||||
<div class="bg-white p-5 rounded-lg shadow-xl" style="width: 90%; max-width: 600px">
|
||||
<h3 class="text-lg font-semibold mb-4">Crop Image</h3>
|
||||
<image-cropper [imageChangedEvent]="imageChangedEvent" [maintainAspectRatio]="true" [aspectRatio]="16 / 9" format="png" (imageCropped)="imageCropped($event)"></image-cropper>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button (click)="closeModal()" class="mr-2 px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300">Cancel</button>
|
||||
<button (click)="uploadImage()" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<app-confirmation></app-confirmation>
|
||||
|
||||
@@ -55,3 +55,52 @@
|
||||
cursor: pointer; /* Verwandelt den Cursor in eine Hand, um Interaktivität anzudeuten */
|
||||
border-radius: 8px; /* Optional: Abrunden der linken unteren Ecke für ästhetische Zwecke */
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
aspect-ratio: 16 / 9;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 8px;
|
||||
transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
|
||||
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.grid-item:active {
|
||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.image-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cdk-drag-preview {
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.cdk-drag-placeholder {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.cdk-drag-animating {
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.grid-container.cdk-drop-list-dragging .grid-item:not(.cdk-drag-placeholder) {
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, ElementRef, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||
import { createDefaultCommercialPropertyListing, map2User, routeListingWithState } from '../../../utils/utils';
|
||||
|
||||
import { CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { ViewportRuler } from '@angular/cdk/scrolling';
|
||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { AngularCropperjsModule } from 'angular-cropperjs';
|
||||
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { NgxCurrencyDirective } from 'ngx-currency';
|
||||
import { ImageCroppedEvent, ImageCropperComponent } from 'ngx-image-cropper';
|
||||
import { QuillModule } from 'ngx-quill';
|
||||
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { AutoCompleteCompleteEvent, ImageProperty, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { ConfirmationComponent } from '../../../components/confirmation/confirmation.component';
|
||||
import { ConfirmationService } from '../../../components/confirmation/confirmation.service';
|
||||
import { DragDropMixedComponent } from '../../../components/drag-drop-mixed/drag-drop-mixed.component';
|
||||
import { MessageService } from '../../../components/message/message.service';
|
||||
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
||||
import { GeoService } from '../../../services/geo.service';
|
||||
import { ImageService } from '../../../services/image.service';
|
||||
@@ -26,13 +30,15 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||
@Component({
|
||||
selector: 'commercial-property-listing',
|
||||
standalone: true,
|
||||
imports: [SharedModule, ArrayToStringPipe, AngularCropperjsModule, DragDropModule, MixedCdkDragDropModule, QuillModule, NgxCurrencyDirective, NgSelectModule],
|
||||
imports: [SharedModule, ArrayToStringPipe, DragDropModule, QuillModule, NgxCurrencyDirective, NgSelectModule, ImageCropperComponent, ConfirmationComponent, DragDropMixedComponent],
|
||||
providers: [],
|
||||
templateUrl: './edit-commercial-property-listing.component.html',
|
||||
styleUrl: './edit-commercial-property-listing.component.scss',
|
||||
})
|
||||
export class EditCommercialPropertyListingComponent {
|
||||
// @ViewChild(FileUpload) public fileUpload: FileUpload;
|
||||
@ViewChild('fileInput') fileInput!: ElementRef<HTMLInputElement>;
|
||||
|
||||
listingsCategory = 'commercialProperty';
|
||||
category: string;
|
||||
location: string;
|
||||
@@ -74,6 +80,10 @@ export class EditCommercialPropertyListingComponent {
|
||||
quillModules = {
|
||||
toolbar: [['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ color: [] }, { background: [] }], ['clean']],
|
||||
};
|
||||
showModal = false;
|
||||
imageChangedEvent: any = '';
|
||||
croppedImage: Blob | null = null;
|
||||
|
||||
constructor(
|
||||
public selectOptions: SelectOptionsService,
|
||||
private router: Router,
|
||||
@@ -87,6 +97,10 @@ export class EditCommercialPropertyListingComponent {
|
||||
|
||||
private route: ActivatedRoute,
|
||||
private keycloakService: KeycloakService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private confirmationService: ConfirmationService,
|
||||
private messageService: MessageService,
|
||||
private viewportRuler: ViewportRuler,
|
||||
) {
|
||||
// Abonniere Router-Events, um den aktiven Link zu ermitteln
|
||||
this.router.events.subscribe(event => {
|
||||
@@ -130,7 +144,43 @@ export class EditCommercialPropertyListingComponent {
|
||||
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query, this.listing.state));
|
||||
this.suggestions = result.map(r => r.city).slice(0, 5);
|
||||
}
|
||||
openFileDialog() {
|
||||
this.fileInput.nativeElement.click();
|
||||
}
|
||||
|
||||
fileChangeEvent(event: any): void {
|
||||
this.imageChangedEvent = event;
|
||||
this.showModal = true;
|
||||
}
|
||||
|
||||
imageCropped(event: ImageCroppedEvent) {
|
||||
this.croppedImage = event.blob;
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
this.showModal = false;
|
||||
this.imageChangedEvent = '';
|
||||
this.croppedImage = null;
|
||||
}
|
||||
|
||||
uploadImage() {
|
||||
if (this.croppedImage) {
|
||||
// Convert base64 to blob
|
||||
// Replace with your actual API endpoint
|
||||
this.imageService.uploadImage(this.croppedImage, 'uploadPropertyPicture', this.listing.imagePath, this.listing.serialId).subscribe(
|
||||
async response => {
|
||||
console.log('Upload successful', response);
|
||||
setTimeout(async () => {
|
||||
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'));
|
||||
});
|
||||
this.closeModal();
|
||||
},
|
||||
error => {
|
||||
console.error('Upload failed', error);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
// select(event: any) {
|
||||
// const imageUrl = URL.createObjectURL(event.files[0]);
|
||||
// getImageDimensions(imageUrl).then(dimensions => {
|
||||
@@ -174,36 +224,22 @@ export class EditCommercialPropertyListingComponent {
|
||||
// });
|
||||
// }
|
||||
|
||||
// deleteConfirm(imageName: string) {
|
||||
// this.confirmationService.confirm({
|
||||
// target: event.target as EventTarget,
|
||||
// message: `Do you want to delete this image ${imageName}?`,
|
||||
// header: 'Delete Confirmation',
|
||||
// icon: 'pi pi-info-circle',
|
||||
// acceptButtonStyleClass: 'p-button-danger p-button-text',
|
||||
// rejectButtonStyleClass: 'p-button-text p-button-text',
|
||||
// acceptIcon: 'none',
|
||||
// rejectIcon: 'none',
|
||||
|
||||
// accept: async () => {
|
||||
// this.listing.imageOrder = this.listing.imageOrder.filter(item => item !== imageName);
|
||||
// await Promise.all([this.imageService.deleteListingImage(this.listing.imagePath, this.listing.serialId, imageName), this.listingsService.save(this.listing, 'commercialProperty')]);
|
||||
// this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
|
||||
// },
|
||||
// reject: () => {
|
||||
// // this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' });
|
||||
// console.log('deny');
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
onDrop(event: { previousIndex: number; currentIndex: number }) {
|
||||
moveItemInArray(this.listing.imageOrder, event.previousIndex, event.currentIndex);
|
||||
async deleteConfirm(imageName: string) {
|
||||
const confirmed = await this.confirmationService.showConfirmation('Are you sure you want to delete this image?');
|
||||
if (confirmed) {
|
||||
this.listing.imageOrder = this.listing.imageOrder.filter(item => item !== imageName);
|
||||
await Promise.all([this.imageService.deleteListingImage(this.listing.imagePath, this.listing.serialId, imageName), this.listingsService.save(this.listing, 'commercialProperty')]);
|
||||
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'));
|
||||
this.messageService.showMessage('Image deleted');
|
||||
} else {
|
||||
console.log('deny');
|
||||
}
|
||||
}
|
||||
|
||||
changeListingCategory(value: 'business' | 'commercialProperty') {
|
||||
routeListingWithState(this.router, value, this.listing);
|
||||
}
|
||||
drop(event: CdkDragDrop<string[]>) {
|
||||
moveItemInArray(this.listing.imageOrder, event.previousIndex, event.currentIndex);
|
||||
imageOrderChanged(imageOrder: string[]) {
|
||||
this.listing.imageOrder = imageOrder;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user