fixing and styleing
This commit is contained in:
@@ -16,6 +16,7 @@ export interface CreateDialogData {
|
|||||||
type: 'text' | 'number' | 'toggle';
|
type: 'text' | 'number' | 'toggle';
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
value?: any;
|
value?: any;
|
||||||
|
hint?: string;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +51,9 @@ export interface CreateDialogData {
|
|||||||
[type]="field.type"
|
[type]="field.type"
|
||||||
[(ngModel)]="formData[field.name]"
|
[(ngModel)]="formData[field.name]"
|
||||||
[required]="!!field.required">
|
[required]="!!field.required">
|
||||||
|
@if (field.hint) {
|
||||||
|
<mat-hint>{{ field.hint }}</mat-hint>
|
||||||
|
}
|
||||||
@if (field.required && !formData[field.name]) {
|
@if (field.required && !formData[field.name]) {
|
||||||
<mat-error>{{ field.label }} is required</mat-error>
|
<mat-error>{{ field.label }} is required</mat-error>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ export interface Subcategory {
|
|||||||
visible: boolean;
|
visible: boolean;
|
||||||
priority: number;
|
priority: number;
|
||||||
img?: string;
|
img?: string;
|
||||||
|
/** Root-level category this subcategory belongs to */
|
||||||
categoryId: string;
|
categoryId: string;
|
||||||
|
/** Direct parent ID — could be a category ID or a parent subcategory ID */
|
||||||
|
parentId?: string;
|
||||||
itemCount?: number;
|
itemCount?: number;
|
||||||
subcategories?: Subcategory[];
|
subcategories?: Subcategory[];
|
||||||
hasItems?: boolean;
|
hasItems?: boolean;
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export class CategoryEditorComponent implements OnInit {
|
|||||||
|
|
||||||
dialogRef.afterClosed().subscribe(result => {
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
this.apiService.createSubcategory(this.categoryId(), result).subscribe({
|
this.apiService.createSubcategory(this.categoryId(), 'category', result).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackBar.open('Subcategory created!', 'Close', { duration: 2000 });
|
this.snackBar.open('Subcategory created!', 'Close', { duration: 2000 });
|
||||||
this.loadCategory();
|
this.loadCategory();
|
||||||
|
|||||||
@@ -61,7 +61,6 @@
|
|||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 0 0 8px 8px;
|
border-radius: 0 0 8px 8px;
|
||||||
gap: 1.5rem;
|
|
||||||
|
|
||||||
.full-width {
|
.full-width {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -125,17 +125,21 @@ export class ItemEditorComponent implements OnInit {
|
|||||||
|
|
||||||
pathSegments.unshift(subcategory.id); // Add to beginning
|
pathSegments.unshift(subcategory.id); // Add to beginning
|
||||||
|
|
||||||
// Check if this subcategory has a parent subcategory or belongs to a category
|
// parentId = direct parent (another subcategory or the root category)
|
||||||
if (subcategory.categoryId && subcategory.categoryId.startsWith('cat')) {
|
// categoryId = always the root category
|
||||||
// This is directly under a category, add category and stop
|
const isDirectCategoryChild =
|
||||||
|
!subcategory.parentId || subcategory.parentId === subcategory.categoryId;
|
||||||
|
|
||||||
|
if (isDirectCategoryChild) {
|
||||||
|
// This subcategory sits directly under a root category
|
||||||
const category = await this.apiService.getCategory(subcategory.categoryId).toPromise();
|
const category = await this.apiService.getCategory(subcategory.categoryId).toPromise();
|
||||||
if (category) {
|
if (category) {
|
||||||
pathSegments.unshift(category.id);
|
pathSegments.unshift(category.id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// This is under another subcategory, continue traversing
|
// Still inside a parent subcategory — keep traversing up
|
||||||
currentSubcategoryId = subcategory.categoryId;
|
currentSubcategoryId = subcategory.parentId!;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error building path:', err);
|
console.error('Error building path:', err);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<button mat-icon-button (click)="goBack()">
|
<button mat-icon-button (click)="goBack()">
|
||||||
<mat-icon>arrow_back</mat-icon>
|
<mat-icon>arrow_back</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<span class="toolbar-title">Items List</span>
|
<span class="toolbar-title">{{ subcategoryName() || 'Items' }}</span>
|
||||||
<span class="toolbar-spacer"></span>
|
<span class="toolbar-spacer"></span>
|
||||||
<button mat-mini-fab color="accent" (click)="addItem()">
|
<button mat-mini-fab color="accent" (click)="addItem()">
|
||||||
<mat-icon>add</mat-icon>
|
<mat-icon>add</mat-icon>
|
||||||
@@ -133,4 +133,6 @@
|
|||||||
<p>No items found</p>
|
<p>No items found</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div #scrollSentinel class="scroll-sentinel"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbar-spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.filters-bar {
|
.filters-bar {
|
||||||
@@ -267,3 +271,8 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scroll-sentinel {
|
||||||
|
height: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, signal, HostListener } from '@angular/core';
|
import { Component, OnInit, AfterViewInit, OnDestroy, signal, ViewChild, ElementRef } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
@@ -39,14 +39,18 @@ import { ConfirmDialogComponent } from '../../components/confirm-dialog/confirm-
|
|||||||
templateUrl: './items-list.component.html',
|
templateUrl: './items-list.component.html',
|
||||||
styleUrls: ['./items-list.component.scss']
|
styleUrls: ['./items-list.component.scss']
|
||||||
})
|
})
|
||||||
export class ItemsListComponent implements OnInit {
|
export class ItemsListComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
items = signal<Item[]>([]);
|
items = signal<Item[]>([]);
|
||||||
loading = signal(false);
|
loading = signal(false);
|
||||||
hasMore = signal(true);
|
hasMore = signal(false);
|
||||||
page = signal(1);
|
page = signal(1);
|
||||||
searchQuery = signal('');
|
searchQuery = '';
|
||||||
visibilityFilter = signal<boolean | undefined>(undefined);
|
visibilityFilter: boolean | undefined = undefined;
|
||||||
selectedItems = signal<Set<string>>(new Set());
|
selectedItems = signal<Set<string>>(new Set());
|
||||||
|
subcategoryName = signal<string>('');
|
||||||
|
|
||||||
|
@ViewChild('scrollSentinel') scrollSentinel!: ElementRef;
|
||||||
|
private intersectionObserver?: IntersectionObserver;
|
||||||
|
|
||||||
subcategoryId = signal<string>('');
|
subcategoryId = signal<string>('');
|
||||||
projectId = signal<string>('');
|
projectId = signal<string>('');
|
||||||
@@ -68,14 +72,17 @@ export class ItemsListComponent implements OnInit {
|
|||||||
|
|
||||||
this.route.params.subscribe(params => {
|
this.route.params.subscribe(params => {
|
||||||
this.subcategoryId.set(params['subcategoryId']);
|
this.subcategoryId.set(params['subcategoryId']);
|
||||||
|
this.page.set(1);
|
||||||
|
this.items.set([]);
|
||||||
|
this.selectedItems.set(new Set());
|
||||||
|
this.subcategoryName.set('');
|
||||||
|
this.loadSubcategoryName();
|
||||||
this.loadItems();
|
this.loadItems();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadItems(append = false) {
|
loadItems(append = false) {
|
||||||
if (this.loading() || (!append && this.items().length > 0)) {
|
if (this.loading()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
const currentPage = append ? this.page() + 1 : 1;
|
const currentPage = append ? this.page() + 1 : 1;
|
||||||
@@ -84,9 +91,9 @@ export class ItemsListComponent implements OnInit {
|
|||||||
this.subcategoryId(),
|
this.subcategoryId(),
|
||||||
currentPage,
|
currentPage,
|
||||||
20,
|
20,
|
||||||
this.searchQuery() || undefined,
|
this.searchQuery || undefined,
|
||||||
{
|
{
|
||||||
visible: this.visibilityFilter(),
|
visible: this.visibilityFilter,
|
||||||
tags: []
|
tags: []
|
||||||
}
|
}
|
||||||
).subscribe({
|
).subscribe({
|
||||||
@@ -108,16 +115,31 @@ export class ItemsListComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:scroll', [])
|
ngAfterViewInit() {
|
||||||
onScroll() {
|
this.intersectionObserver = new IntersectionObserver(
|
||||||
const scrollPosition = window.pageYOffset + window.innerHeight;
|
entries => {
|
||||||
const documentHeight = document.documentElement.scrollHeight;
|
if (entries[0].isIntersecting && this.hasMore() && !this.loading()) {
|
||||||
|
this.loadItems(true);
|
||||||
if (scrollPosition >= documentHeight - 200 && this.hasMore() && !this.loading()) {
|
}
|
||||||
this.loadItems(true);
|
},
|
||||||
|
{ rootMargin: '200px', threshold: 0 }
|
||||||
|
);
|
||||||
|
if (this.scrollSentinel?.nativeElement) {
|
||||||
|
this.intersectionObserver.observe(this.scrollSentinel.nativeElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.intersectionObserver?.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSubcategoryName() {
|
||||||
|
this.apiService.getSubcategory(this.subcategoryId()).subscribe({
|
||||||
|
next: (sub) => this.subcategoryName.set(sub.name),
|
||||||
|
error: () => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onSearch() {
|
onSearch() {
|
||||||
this.page.set(1);
|
this.page.set(1);
|
||||||
this.items.set([]);
|
this.items.set([]);
|
||||||
@@ -205,6 +227,8 @@ export class ItemsListComponent implements OnInit {
|
|||||||
this.apiService.createItem(subcategoryId, result).subscribe({
|
this.apiService.createItem(subcategoryId, result).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackBar.open('Item created successfully', 'Close', { duration: 3000 });
|
this.snackBar.open('Item created successfully', 'Close', { duration: 3000 });
|
||||||
|
this.page.set(1);
|
||||||
|
this.items.set([]);
|
||||||
this.loadItems();
|
this.loadItems();
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -234,6 +258,8 @@ export class ItemsListComponent implements OnInit {
|
|||||||
this.apiService.deleteItem(item.id).subscribe({
|
this.apiService.deleteItem(item.id).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackBar.open('Item deleted successfully', 'Close', { duration: 3000 });
|
this.snackBar.open('Item deleted successfully', 'Close', { duration: 3000 });
|
||||||
|
this.page.set(1);
|
||||||
|
this.items.set([]);
|
||||||
this.loadItems();
|
this.loadItems();
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<button mat-icon-button (click)="goBack()">
|
<button mat-icon-button (click)="goBack()">
|
||||||
<mat-icon>arrow_back</mat-icon>
|
<mat-icon>arrow_back</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<span>Project: {{ projectId() }}</span>
|
<span>{{ project()?.displayName || projectId() }}</span>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
<mat-sidenav-container class="sidenav-container">
|
<mat-sidenav-container class="sidenav-container">
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #subcategoryTree let-nodes="nodes" let-level="level">
|
<ng-template #subcategoryTree let-nodes="nodes" let-level="level">
|
||||||
<div class="subcategories" [style.padding-left.rem]="level * 1.5">
|
<div class="subcategories">
|
||||||
@for (subNode of nodes; track subNode.id) {
|
@for (subNode of nodes; track subNode.id) {
|
||||||
<div>
|
<div>
|
||||||
<div class="node-content subcategory-node" [class.selected]="selectedNodeId() === subNode.id">
|
<div class="node-content subcategory-node" [class.selected]="selectedNodeId() === subNode.id">
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ mat-toolbar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.categories-sidebar {
|
.categories-sidebar {
|
||||||
width: 380px;
|
width: 420px;
|
||||||
border-right: 1px solid #e0e0e0;
|
border-right: 1px solid #e0e0e0;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
@@ -31,6 +31,8 @@ mat-toolbar {
|
|||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// icon centering handled globally via styles.scss
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-container {
|
.loading-container {
|
||||||
@@ -68,6 +70,10 @@ mat-toolbar {
|
|||||||
background-color: #bbdefb;
|
background-color: #bbdefb;
|
||||||
border-left: 4px solid #1976d2;
|
border-left: 4px solid #1976d2;
|
||||||
padding-left: calc(0.5rem - 4px);
|
padding-left: calc(0.5rem - 4px);
|
||||||
|
|
||||||
|
.node-actions {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #90caf9;
|
background-color: #90caf9;
|
||||||
@@ -85,13 +91,12 @@ mat-toolbar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.subcategory-node {
|
&.subcategory-node {
|
||||||
padding-left: 3rem;
|
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background-color: #bbdefb;
|
background-color: #bbdefb;
|
||||||
padding-left: calc(3rem - 4px);
|
border-left: 4px solid #1976d2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,8 +115,8 @@ mat-toolbar {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
opacity: 0.7;
|
opacity: 0;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.15s;
|
||||||
|
|
||||||
mat-slide-toggle {
|
mat-slide-toggle {
|
||||||
transform: scale(0.75);
|
transform: scale(0.75);
|
||||||
@@ -119,15 +124,16 @@ mat-toolbar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
// width: 32px;
|
--mdc-icon-button-state-layer-size: 30px;
|
||||||
// height: 32px;
|
--mdc-icon-button-icon-size: 18px;
|
||||||
// line-height: 32px;
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
mat-icon {
|
mat-icon {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
line-height: 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -143,6 +149,10 @@ mat-toolbar {
|
|||||||
|
|
||||||
.subcategories {
|
.subcategories {
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 2px solid #e3e8ef;
|
||||||
|
margin-left: 1.25rem;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -253,8 +253,8 @@ export class ProjectViewComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentId = parentNode.type === 'category' ? parentNode.id : parentNode.id;
|
const parentType = parentNode.type === 'category' ? 'category' : 'subcategory';
|
||||||
this.apiService.createSubcategory(parentId, result).subscribe({
|
this.apiService.createSubcategory(parentNode.id, parentType, result).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackBar.open('Subcategory created!', 'Close', { duration: 2000 });
|
this.snackBar.open('Subcategory created!', 'Close', { duration: 2000 });
|
||||||
this.loadCategories();
|
this.loadCategories();
|
||||||
|
|||||||
@@ -15,11 +15,7 @@
|
|||||||
@if (saving()) {
|
@if (saving()) {
|
||||||
<span class="save-indicator">Saving...</span>
|
<span class="save-indicator">Saving...</span>
|
||||||
}
|
}
|
||||||
<button mat-raised-button color="accent" (click)="viewItems()">
|
<button mat-icon-button color="warn" (click)="deleteSubcategory()" matTooltip="Delete Subcategory">
|
||||||
<mat-icon>list</mat-icon>
|
|
||||||
View Items
|
|
||||||
</button>
|
|
||||||
<button mat-icon-button color="warn" (click)="deleteSubcategory()">
|
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,10 +105,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="items-section">
|
<div class="items-section">
|
||||||
<button mat-raised-button color="primary" (click)="viewItems()">
|
@if (subcategory()!.subcategories?.length) {
|
||||||
<mat-icon>list</mat-icon>
|
<p class="no-items-note">
|
||||||
View Items ({{ subcategory()!.itemCount || 0 }})
|
<mat-icon>account_tree</mat-icon>
|
||||||
</button>
|
This subcategory has child subcategories — items can only be added to leaf nodes.
|
||||||
|
</p>
|
||||||
|
} @else {
|
||||||
|
<button mat-raised-button color="primary" (click)="viewItems()">
|
||||||
|
<mat-icon>{{ subcategory()!.hasItems ? 'list' : 'add' }}</mat-icon>
|
||||||
|
{{ subcategory()!.hasItems ? 'View Items (' + (subcategory()!.itemCount || 0) + ')' : 'Add Items' }}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,4 +115,19 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-items-note {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,12 @@ export class SubcategoryEditorComponent implements OnInit {
|
|||||||
this.apiService.deleteSubcategory(sub.id).subscribe({
|
this.apiService.deleteSubcategory(sub.id).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackBar.open('Subcategory deleted successfully', 'Close', { duration: 3000 });
|
this.snackBar.open('Subcategory deleted successfully', 'Close', { duration: 3000 });
|
||||||
this.router.navigate(['/project', this.projectId(), 'category', sub.categoryId]);
|
// Navigate to the direct parent (subcategory) if parentId exists, otherwise the root category
|
||||||
|
if (sub.parentId && sub.parentId !== sub.categoryId) {
|
||||||
|
this.router.navigate(['/project', this.projectId(), 'subcategory', sub.parentId]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/project', this.projectId(), 'category', sub.categoryId]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error: (err: any) => {
|
error: (err: any) => {
|
||||||
console.error('Error deleting subcategory:', err);
|
console.error('Error deleting subcategory:', err);
|
||||||
|
|||||||
@@ -103,9 +103,12 @@ export class ApiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createSubcategory(categoryId: string, data: Partial<Subcategory>): Observable<Subcategory> {
|
createSubcategory(parentId: string, parentType: 'category' | 'subcategory', data: Partial<Subcategory>): Observable<Subcategory> {
|
||||||
if (environment.useMockData) return this.mockService.createSubcategory(categoryId, data);
|
if (environment.useMockData) return this.mockService.createSubcategory(parentId, data);
|
||||||
return this.http.post<Subcategory>(`${this.API_BASE}/categories/${categoryId}/subcategories`, data).pipe(
|
const endpoint = parentType === 'category'
|
||||||
|
? `${this.API_BASE}/categories/${parentId}/subcategories`
|
||||||
|
: `${this.API_BASE}/subcategories/${parentId}/subcategories`;
|
||||||
|
return this.http.post<Subcategory>(endpoint, data).pipe(
|
||||||
catchError(this.handleError)
|
catchError(this.handleError)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,7 +273,8 @@ export class MockDataService {
|
|||||||
visible: data.visible ?? true,
|
visible: data.visible ?? true,
|
||||||
priority: data.priority || 99,
|
priority: data.priority || 99,
|
||||||
img: data.img,
|
img: data.img,
|
||||||
categoryId: parentId,
|
categoryId: parentId, // will be root category ID after backend resolves; mock keeps direct parent for simplicity
|
||||||
|
parentId: parentId,
|
||||||
itemCount: 0
|
itemCount: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,15 +22,41 @@ button[mat-raised-button], button[mat-flat-button] {
|
|||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Icon buttons — always center the icon in its circle
|
||||||
button[mat-icon-button] {
|
button[mat-icon-button] {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
|
||||||
|
.mat-icon {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
line-height: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.04);
|
background-color: rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button[mat-mini-fab] {
|
button[mat-mini-fab] {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.25) !important;
|
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.25) !important;
|
||||||
|
|
||||||
|
.mat-icon {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
line-height: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.35) !important;
|
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.35) !important;
|
||||||
}
|
}
|
||||||
@@ -55,8 +81,8 @@ mat-card {
|
|||||||
|
|
||||||
// Scrollbar styling
|
// Scrollbar styling
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 10px;
|
width: 6px;
|
||||||
height: 10px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
@@ -64,11 +90,11 @@ mat-card {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #888;
|
background: #aaa;
|
||||||
border-radius: 5px;
|
border-radius: 6px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #555;
|
background: #666;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +111,11 @@ mat-card {
|
|||||||
--mat-snack-bar-button-color: white !important;
|
--mat-snack-bar-button-color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Common editor layout helpers
|
||||||
|
.editor-page {
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.toast-warning {
|
.toast-warning {
|
||||||
--mdc-snackbar-container-color: #ff9800 !important;
|
--mdc-snackbar-container-color: #ff9800 !important;
|
||||||
--mdc-snackbar-supporting-text-color: white !important;
|
--mdc-snackbar-supporting-text-color: white !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user