Service tabs updated
This commit is contained in:
@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.ServiceTile;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.HttpResponse;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.enumeration.ServiceTileCategory;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.service.ServiceTileService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -26,10 +27,11 @@ public class ServiceTileResource {
|
||||
public ResponseEntity<ServiceTile> addServiceTile(
|
||||
@RequestParam String title,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam ServiceTileCategory category,
|
||||
@RequestParam(required = false) Integer displayOrder) {
|
||||
|
||||
log.info("Adding new service tile with title: {}", title);
|
||||
ServiceTile serviceTile = serviceTileService.addServiceTile(title, description, displayOrder);
|
||||
log.info("Adding new service tile with title: {} and category: {}", title, category);
|
||||
ServiceTile serviceTile = serviceTileService.addServiceTile(title, description, category, displayOrder);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(serviceTile);
|
||||
}
|
||||
|
||||
@ -39,10 +41,11 @@ public class ServiceTileResource {
|
||||
@PathVariable Long id,
|
||||
@RequestParam String title,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam ServiceTileCategory category,
|
||||
@RequestParam(required = false) Integer displayOrder) {
|
||||
|
||||
log.info("Updating service tile with id: {}", id);
|
||||
ServiceTile serviceTile = serviceTileService.updateServiceTile(id, title, description, displayOrder);
|
||||
ServiceTile serviceTile = serviceTileService.updateServiceTile(id, title, description, category, displayOrder);
|
||||
return ResponseEntity.ok(serviceTile);
|
||||
}
|
||||
|
||||
@ -83,7 +86,6 @@ public class ServiceTileResource {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/{id}/toggle-active")
|
||||
@PreAuthorize("hasAnyAuthority('user:update')")
|
||||
public ResponseEntity<ServiceTile> toggleActiveStatus(@PathVariable Long id) {
|
||||
|
||||
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.enumeration.ServiceTileCategory;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.io.Serializable;
|
||||
@ -27,6 +28,10 @@ public class ServiceTile implements Serializable {
|
||||
@Column(length = 1000)
|
||||
private String description;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private ServiceTileCategory category;
|
||||
|
||||
@JsonProperty("isActive")
|
||||
@Column(name = "is_active")
|
||||
private boolean isActive;
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.enumeration;
|
||||
|
||||
public enum ServiceTileCategory {
|
||||
TRAUMA_CARE("Trauma Care"),
|
||||
ACUTE_CARE_SURGERY("Acute Care Surgery");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
ServiceTileCategory(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,15 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.service;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.ServiceTile;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.enumeration.ServiceTileCategory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ServiceTileService {
|
||||
|
||||
ServiceTile addServiceTile(String title, String description, Integer displayOrder);
|
||||
ServiceTile addServiceTile(String title, String description, ServiceTileCategory category, Integer displayOrder);
|
||||
|
||||
ServiceTile updateServiceTile(Long id, String title, String description, Integer displayOrder);
|
||||
ServiceTile updateServiceTile(Long id, String title, String description, ServiceTileCategory category, Integer displayOrder);
|
||||
|
||||
List<ServiceTile> getActiveServiceTiles();
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ package net.shyshkin.study.fullstack.supportportal.backend.service.impl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.ServiceTile;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.enumeration.ServiceTileCategory;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.exception.domain.ServiceTileNotFoundException;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.repository.ServiceTileRepository;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.service.ServiceTileService;
|
||||
@ -20,25 +21,27 @@ public class ServiceTileServiceImpl implements ServiceTileService {
|
||||
private final ServiceTileRepository serviceTileRepository;
|
||||
|
||||
@Override
|
||||
public ServiceTile addServiceTile(String title, String description, Integer displayOrder) {
|
||||
log.info("Adding new service tile with title: {}", title);
|
||||
public ServiceTile addServiceTile(String title, String description, ServiceTileCategory category, Integer displayOrder) {
|
||||
log.info("Adding new service tile with title: {} and category: {}", title, category);
|
||||
|
||||
ServiceTile serviceTile = new ServiceTile();
|
||||
serviceTile.setTitle(title);
|
||||
serviceTile.setDescription(description);
|
||||
serviceTile.setCategory(category);
|
||||
serviceTile.setDisplayOrder(displayOrder != null ? displayOrder : 0);
|
||||
serviceTile.setActive(true); // New tiles are active by default
|
||||
serviceTile.setActive(true);
|
||||
|
||||
return serviceTileRepository.save(serviceTile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceTile updateServiceTile(Long id, String title, String description, Integer displayOrder) {
|
||||
public ServiceTile updateServiceTile(Long id, String title, String description, ServiceTileCategory category, Integer displayOrder) {
|
||||
log.info("Updating service tile with id: {}", id);
|
||||
|
||||
ServiceTile serviceTile = getServiceTileById(id);
|
||||
serviceTile.setTitle(title);
|
||||
serviceTile.setDescription(description);
|
||||
serviceTile.setCategory(category);
|
||||
|
||||
if (displayOrder != null) {
|
||||
serviceTile.setDisplayOrder(displayOrder);
|
||||
|
||||
@ -426,6 +426,42 @@
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
}
|
||||
/* Add to existing CSS */
|
||||
|
||||
.badge-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.category-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
background: #e0e7ff;
|
||||
color: #4338ca;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Select dropdown styling */
|
||||
select.form-input {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 12px center;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
select.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* Form Styles */
|
||||
.form-section {
|
||||
|
||||
@ -31,6 +31,8 @@
|
||||
<div class="service-tile-info">
|
||||
<div class="service-tile-header-row">
|
||||
<h3>{{ serviceTile?.title }}</h3>
|
||||
<div class="badge-group">
|
||||
<span class="category-badge">{{ getCategoryLabel(serviceTile?.category) }}</span>
|
||||
<span class="status-badge" [class.status-active]="serviceTile?.isActive"
|
||||
[class.status-inactive]="!serviceTile?.isActive">
|
||||
<i class="fa" [class.fa-check-circle]="serviceTile?.isActive"
|
||||
@ -38,6 +40,7 @@
|
||||
{{ serviceTile?.isActive ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="description" *ngIf="serviceTile?.description">
|
||||
{{ serviceTile.description }}</p>
|
||||
<div class="order-badge">Order: {{ serviceTile?.displayOrder }}</div>
|
||||
@ -92,14 +95,20 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="service-tile-detail-card">
|
||||
<div class="detail-item"><span class="detail-label">Title:</span>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Title:</span>
|
||||
<span class="detail-value">{{ selectedServiceTile.title }}</span>
|
||||
</div>
|
||||
<div class="detail-item"><span class="detail-label">Description:</span>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Category:</span>
|
||||
<span class="detail-value">{{ getCategoryLabel(selectedServiceTile.category) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Description:</span>
|
||||
<span class="detail-value">{{ selectedServiceTile.description }}</span>
|
||||
</div>
|
||||
<div class="detail-item"><span class="detail-label">Display
|
||||
Order:</span>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Display Order:</span>
|
||||
<span class="detail-value">{{ selectedServiceTile.displayOrder }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -118,7 +127,8 @@
|
||||
<div class="modal-header">
|
||||
<h3>Add New Service Tile</h3>
|
||||
<button type="button" class="modal-close" data-bs-dismiss="modal">
|
||||
<i class="fa fa-times"></i></button>
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-section">
|
||||
@ -126,11 +136,21 @@
|
||||
<label>Title *</label>
|
||||
<input class="form-input" name="title" ngModel required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Category *</label>
|
||||
<select class="form-input" name="category" ngModel required>
|
||||
<option value="" disabled selected>Select Category</option>
|
||||
<option *ngFor="let cat of categories" [value]="cat.value">{{ cat.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Description * (Separate points with commas)</label>
|
||||
<textarea class="form-textarea" name="description" ngModel required
|
||||
placeholder="e.g., Point one, Point two, Point three"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Display Order</label>
|
||||
<input type="number" class="form-input" name="displayOrder" ngModel value="0">
|
||||
@ -155,7 +175,8 @@
|
||||
<div class="modal-header">
|
||||
<h3>Edit Service Tile</h3>
|
||||
<button type="button" class="modal-close" data-bs-dismiss="modal">
|
||||
<i class="fa fa-times"></i></button>
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-section">
|
||||
@ -163,11 +184,21 @@
|
||||
<label>Title *</label>
|
||||
<input class="form-input" name="title" [(ngModel)]="selectedServiceTile.title" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Category *</label>
|
||||
<select class="form-input" name="category" [(ngModel)]="selectedServiceTile.category" required>
|
||||
<option value="" disabled>Select Category</option>
|
||||
<option *ngFor="let cat of categories" [value]="cat.value">{{ cat.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Description * (Separate points with commas)</label>
|
||||
<textarea class="form-textarea" name="description" [(ngModel)]="selectedServiceTile.description"
|
||||
required placeholder="e.g., Point one, Point two, Point three"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Display Order</label>
|
||||
<input type="number" class="form-input" name="displayOrder"
|
||||
|
||||
@ -29,7 +29,12 @@ export class ServiceTileComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
public refreshing: boolean = false;
|
||||
private subs = new SubSink();
|
||||
|
||||
// ✅ Modal references (No hidden button clicks anymore)
|
||||
// Category options
|
||||
public categories = [
|
||||
{ value: 'ACUTE_CARE_SURGERY', label: 'Acute Care Surgery' },
|
||||
{ value: 'TRAUMA_CARE', label: 'Trauma Care' }
|
||||
];
|
||||
|
||||
@ViewChild('addServiceTileModal') addServiceTileModal!: ElementRef;
|
||||
@ViewChild('editServiceTileModal') editServiceTileModal!: ElementRef;
|
||||
@ViewChild('viewServiceTileModal') viewServiceTileModal!: ElementRef;
|
||||
@ -50,16 +55,14 @@ export class ServiceTileComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
// ✅ Initialize Bootstrap modal instances
|
||||
this.addModal = new Modal(this.addServiceTileModal.nativeElement);
|
||||
this.editModal = new Modal(this.editServiceTileModal.nativeElement);
|
||||
this.viewModal = new Modal(this.viewServiceTileModal.nativeElement);
|
||||
|
||||
}
|
||||
|
||||
openAddModal(): void {
|
||||
this.addModal.show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.unsubscribe();
|
||||
@ -88,7 +91,7 @@ export class ServiceTileComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
public onSelectServiceTile(serviceTile: ServiceTile): void {
|
||||
this.selectedServiceTile = { ...serviceTile };
|
||||
this.viewModal.show(); // ✅ Show view modal
|
||||
this.viewModal.show();
|
||||
}
|
||||
|
||||
public onAddNewServiceTile(form: NgForm): void {
|
||||
@ -98,8 +101,9 @@ export class ServiceTileComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
this.subs.sink = this.serviceTileService.addServiceTile(formData).subscribe(
|
||||
serviceTile => {
|
||||
this.getServiceTiles(false);
|
||||
this.addModal.hide(); // ✅ Close modal cleanly
|
||||
this.addModal.hide();
|
||||
form.reset();
|
||||
this.notificationService.notify(NotificationType.SUCCESS, 'Service tile created successfully');
|
||||
},
|
||||
(errorResponse: HttpErrorResponse) => {
|
||||
this.sendErrorNotification(errorResponse.error.message);
|
||||
@ -109,7 +113,7 @@ export class ServiceTileComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
public onEditServiceTile(serviceTile: ServiceTile): void {
|
||||
this.selectedServiceTile = { ...serviceTile };
|
||||
this.editModal.show(); // ✅ Show edit modal
|
||||
this.editModal.show();
|
||||
}
|
||||
|
||||
public onUpdateServiceTile(form: NgForm): void {
|
||||
@ -123,7 +127,8 @@ export class ServiceTileComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
this.subs.sink = this.serviceTileService.updateServiceTile(this.selectedServiceTile.id!, formData).subscribe(
|
||||
() => {
|
||||
this.getServiceTiles(false);
|
||||
this.editModal.hide(); // ✅ Close modal cleanly
|
||||
this.editModal.hide();
|
||||
this.notificationService.notify(NotificationType.SUCCESS, 'Service tile updated successfully');
|
||||
},
|
||||
(errorResponse: HttpErrorResponse) => {
|
||||
this.sendErrorNotification(errorResponse.error.message);
|
||||
@ -160,13 +165,23 @@ export class ServiceTileComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
this.serviceTiles = this.serviceTileService.getServiceTilesFromLocalStorage()
|
||||
.filter(t => t.title?.toLowerCase().includes(searchTerm) || t.description?.toLowerCase().includes(searchTerm));
|
||||
.filter(t =>
|
||||
t.title?.toLowerCase().includes(searchTerm) ||
|
||||
t.description?.toLowerCase().includes(searchTerm) ||
|
||||
this.getCategoryLabel(t.category).toLowerCase().includes(searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
public getCategoryLabel(category: string): string {
|
||||
const cat = this.categories.find(c => c.value === category);
|
||||
return cat ? cat.label : category;
|
||||
}
|
||||
|
||||
private createServiceTileFormData(serviceTile: any): FormData {
|
||||
const formData = new FormData();
|
||||
formData.append('title', serviceTile.title || '');
|
||||
formData.append('description', serviceTile.description || '');
|
||||
formData.append('category', serviceTile.category || '');
|
||||
formData.append('displayOrder', (serviceTile.displayOrder || 0).toString());
|
||||
return formData;
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ export interface ServiceTile {
|
||||
id?: number;
|
||||
title: string;
|
||||
description: string;
|
||||
category: 'TRAUMA_CARE' | 'ACUTE_CARE_SURGERY';
|
||||
isActive: boolean;
|
||||
displayOrder?: number;
|
||||
createdDate?: Date;
|
||||
|
||||
Reference in New Issue
Block a user