Employee leaving date fixed.
Image now showing in vouchers fixed.
This commit is contained in:
@@ -64,7 +64,7 @@
|
||||
<mat-datepicker-toggle matSuffix [for]="joiningDate"></mat-datepicker-toggle>
|
||||
<mat-datepicker #joiningDate></mat-datepicker>
|
||||
</mat-form-field>
|
||||
@if (!item().isActive) {
|
||||
@if (!form.isActive().value()) {
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Leaving Date</mat-label>
|
||||
<input matInput [matDatepicker]="leavingDate" [formField]="form.leavingDate" autocomplete="off" />
|
||||
@@ -76,7 +76,7 @@
|
||||
</form>
|
||||
|
||||
<div class="row-container">
|
||||
<button mat-raised-button color="primary" (click)="save()">Save</button>
|
||||
<button mat-raised-button color="primary" [disabled]="form().invalid()" (click)="save()">Save</button>
|
||||
@if (!!item().id) {
|
||||
<button mat-raised-button color="warn" (click)="confirmDelete()">Delete</button>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, inject, afterNextRender, linkedSignal, input, computed } from '@angular/core';
|
||||
import { form as createForm, FormField, FormRoot } from '@angular/forms/signals';
|
||||
import { form as createForm, FormField, FormRoot, required } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
@@ -28,8 +28,8 @@ export interface EmployeeDetailFormData {
|
||||
points: number;
|
||||
isActive: boolean;
|
||||
costCentre: string;
|
||||
joiningDate: string;
|
||||
leavingDate: string | null;
|
||||
joiningDate: Date;
|
||||
leavingDate: Date | null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -70,12 +70,22 @@ export class EmployeeDetailComponent {
|
||||
points: itemVal.points || 0,
|
||||
isActive: itemVal.isActive ?? true,
|
||||
costCentre: itemVal.costCentre?.id ?? '',
|
||||
joiningDate: itemVal.joiningDate,
|
||||
leavingDate: itemVal.leavingDate,
|
||||
joiningDate: itemVal.joiningDate ? moment(itemVal.joiningDate, 'DD-MMM-YYYY').toDate() : new Date(),
|
||||
leavingDate: itemVal.leavingDate ? moment(itemVal.leavingDate, 'DD-MMM-YYYY').toDate() : null,
|
||||
}),
|
||||
});
|
||||
|
||||
form = createForm(this.model);
|
||||
form = createForm(this.model, (f) => {
|
||||
required(f.name);
|
||||
required(f.designation);
|
||||
required(f.salary);
|
||||
required(f.points);
|
||||
required(f.joiningDate);
|
||||
required(f.leavingDate, {
|
||||
when: ({ valueOf }) => !valueOf(f.isActive),
|
||||
message: 'Leaving date is required',
|
||||
});
|
||||
});
|
||||
|
||||
costCentresResource = this.costCentreService.list();
|
||||
costCentres = computed(() => this.costCentresResource.value() || []);
|
||||
@@ -87,6 +97,11 @@ export class EmployeeDetailComponent {
|
||||
}
|
||||
|
||||
save() {
|
||||
if (this.form().invalid()) {
|
||||
const errorMsg = this.form().errorSummary()[0]?.message || 'Leaving date is required';
|
||||
this.snackBar.open(errorMsg, 'Danger');
|
||||
return;
|
||||
}
|
||||
this.ser.saveOrUpdate(this.getItem()).subscribe({
|
||||
next: () => {
|
||||
this.snackBar.open('', 'Success');
|
||||
@@ -133,7 +148,8 @@ export class EmployeeDetailComponent {
|
||||
item.isActive = formValue.isActive;
|
||||
item.costCentre.id = formValue.costCentre ?? '';
|
||||
item.joiningDate = moment(formValue.joiningDate).format('DD-MMM-YYYY');
|
||||
item.leavingDate = item.isActive ? null : moment(formValue.leavingDate).format('DD-MMM-YYYY');
|
||||
item.leavingDate =
|
||||
item.isActive || !formValue.leavingDate ? null : moment(formValue.leavingDate).format('DD-MMM-YYYY');
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,242 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.img-container {
|
||||
/* Attachments Section */
|
||||
.attachments-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin: 6px 0 16px;
|
||||
}
|
||||
|
||||
.attachments-header {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--mat-sys-on-surface-variant, #64748b);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.attachments-header-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-primary, #0284c7);
|
||||
}
|
||||
|
||||
.attachments-badge {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 1px 7px;
|
||||
border-radius: 9999px;
|
||||
background: var(--mat-sys-secondary-container, #e2e8f0);
|
||||
color: var(--mat-sys-on-secondary-container, #1e293b);
|
||||
}
|
||||
|
||||
.attachments-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.attachment-card {
|
||||
position: relative;
|
||||
width: 110px;
|
||||
height: 155px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--mat-sys-outline-variant, #e2e8f0);
|
||||
background: var(--mat-sys-surface, #ffffff);
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.img-container .overlay {
|
||||
display: none;
|
||||
.attachment-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.img-container:hover > .overlay {
|
||||
display: inline-block;
|
||||
.thumbnail-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary, #0284c7);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.thumbnail-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:hover .thumbnail-img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.zoom-overlay {
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
top: 0;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:hover .zoom-overlay,
|
||||
.thumbnail-wrapper:focus-visible .zoom-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.zoom-icon {
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
min-width: 32px;
|
||||
padding: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
color: #dc2626;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease,
|
||||
transform 0.15s ease;
|
||||
z-index: 3;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.attachment-card:hover .delete-btn,
|
||||
.attachment-card:focus-within .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn:hover,
|
||||
.attachment-card .delete-btn:focus-visible {
|
||||
background: #dc2626;
|
||||
color: #ffffff;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.upload-tile {
|
||||
width: 110px;
|
||||
height: 155px;
|
||||
border-radius: 12px;
|
||||
border: 2px dashed var(--mat-sys-outline, #cbd5e1);
|
||||
background: var(--mat-sys-surface-container-low, #f8fafc);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-tile:hover {
|
||||
border-color: var(--mat-sys-primary, #0284c7);
|
||||
background: rgba(2, 132, 199, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.upload-tile:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary, #0284c7);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.upload-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.upload-icon-circle {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: var(--mat-sys-primary-container, #e0f2fe);
|
||||
color: var(--mat-sys-on-primary-container, #0284c7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-tile:hover .upload-icon-circle {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.upload-icon-circle mat-icon {
|
||||
font-size: 22px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.upload-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--mat-sys-on-surface, #1e293b);
|
||||
}
|
||||
|
||||
.upload-subtitle {
|
||||
font-size: 10px;
|
||||
color: var(--mat-sys-on-surface-variant, #64748b);
|
||||
}
|
||||
|
||||
.file-input-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.gold {
|
||||
|
||||
@@ -131,29 +131,70 @@
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
@for (item of form.files().value(); track item) {
|
||||
<div class="img-container" class="flex-auto basis-1-5">
|
||||
<img
|
||||
[src]="item.thumbnail"
|
||||
alt="img"
|
||||
(click)="zoomImage(item)"
|
||||
(keyup.enter)="zoomImage(item)"
|
||||
tabindex="0"
|
||||
/>
|
||||
<button mat-icon-button class="overlay" (click)="deleteImage(item)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div class="img-container" class="flex flex-auto basis-1-5">
|
||||
<label ng-href for="fileUp">
|
||||
<img
|
||||
alt="add"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg=="
|
||||
/>
|
||||
<div class="attachments-section">
|
||||
<div class="attachments-header">
|
||||
<mat-icon class="attachments-header-icon">attach_file</mat-icon>
|
||||
<span class="attachments-title">Attachments</span>
|
||||
@if (form.files().value().length > 0) {
|
||||
<span class="attachments-badge">{{ form.files().value().length }}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="attachments-grid">
|
||||
@for (item of form.files().value(); track item) {
|
||||
<div class="attachment-card">
|
||||
<div
|
||||
class="thumbnail-wrapper"
|
||||
(click)="zoomImage(item)"
|
||||
(keyup.enter)="zoomImage(item)"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-label="View enlarged attachment"
|
||||
title="Click to preview"
|
||||
>
|
||||
<img [src]="item.thumbnail" alt="Journal attachment thumbnail" class="thumbnail-img" />
|
||||
<div class="zoom-overlay">
|
||||
<mat-icon class="zoom-icon">zoom_in</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="delete-btn"
|
||||
(click)="deleteImage(item); $event.stopPropagation()"
|
||||
aria-label="Delete attachment"
|
||||
title="Delete attachment"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<label
|
||||
for="fileUp"
|
||||
class="upload-tile"
|
||||
tabindex="0"
|
||||
(keydown.enter)="fileInput.click()"
|
||||
role="button"
|
||||
aria-label="Upload image attachments"
|
||||
>
|
||||
<div class="upload-content">
|
||||
<div class="upload-icon-circle">
|
||||
<mat-icon>add_photo_alternate</mat-icon>
|
||||
</div>
|
||||
<span class="upload-title">Add Image</span>
|
||||
<span class="upload-subtitle">Click to upload</span>
|
||||
</div>
|
||||
</label>
|
||||
<input type="file" id="fileUp" multiple accept="image/*" style="display: none" (change)="detectFiles($event)" />
|
||||
<input
|
||||
#fileInput
|
||||
type="file"
|
||||
id="fileUp"
|
||||
multiple
|
||||
accept="image/*"
|
||||
class="file-input-hidden"
|
||||
(change)="detectFiles($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -16,19 +16,242 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.img-container {
|
||||
/* Attachments Section */
|
||||
.attachments-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin: 6px 0 16px;
|
||||
}
|
||||
|
||||
.attachments-header {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--mat-sys-on-surface-variant, #64748b);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.attachments-header-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-primary, #0284c7);
|
||||
}
|
||||
|
||||
.attachments-badge {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 1px 7px;
|
||||
border-radius: 9999px;
|
||||
background: var(--mat-sys-secondary-container, #e2e8f0);
|
||||
color: var(--mat-sys-on-secondary-container, #1e293b);
|
||||
}
|
||||
|
||||
.attachments-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.attachment-card {
|
||||
position: relative;
|
||||
width: 110px;
|
||||
height: 155px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--mat-sys-outline-variant, #e2e8f0);
|
||||
background: var(--mat-sys-surface, #ffffff);
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.img-container .overlay {
|
||||
display: none;
|
||||
.attachment-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.img-container:hover > .overlay {
|
||||
display: inline-block;
|
||||
.thumbnail-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary, #0284c7);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.thumbnail-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:hover .thumbnail-img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.zoom-overlay {
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
top: 0;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:hover .zoom-overlay,
|
||||
.thumbnail-wrapper:focus-visible .zoom-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.zoom-icon {
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
min-width: 32px;
|
||||
padding: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
color: #dc2626;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease,
|
||||
transform 0.15s ease;
|
||||
z-index: 3;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.attachment-card:hover .delete-btn,
|
||||
.attachment-card:focus-within .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn:hover,
|
||||
.attachment-card .delete-btn:focus-visible {
|
||||
background: #dc2626;
|
||||
color: #ffffff;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.upload-tile {
|
||||
width: 110px;
|
||||
height: 155px;
|
||||
border-radius: 12px;
|
||||
border: 2px dashed var(--mat-sys-outline, #cbd5e1);
|
||||
background: var(--mat-sys-surface-container-low, #f8fafc);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-tile:hover {
|
||||
border-color: var(--mat-sys-primary, #0284c7);
|
||||
background: rgba(2, 132, 199, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.upload-tile:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary, #0284c7);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.upload-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.upload-icon-circle {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: var(--mat-sys-primary-container, #e0f2fe);
|
||||
color: var(--mat-sys-on-primary-container, #0284c7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-tile:hover .upload-icon-circle {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.upload-icon-circle mat-icon {
|
||||
font-size: 22px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.upload-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--mat-sys-on-surface, #1e293b);
|
||||
}
|
||||
|
||||
.upload-subtitle {
|
||||
font-size: 10px;
|
||||
color: var(--mat-sys-on-surface-variant, #64748b);
|
||||
}
|
||||
|
||||
.file-input-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.gold {
|
||||
|
||||
@@ -134,29 +134,70 @@
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
@for (item of form.files().value(); track item) {
|
||||
<div class="img-container" class="flex-auto basis-1-5">
|
||||
<img
|
||||
[src]="item.thumbnail"
|
||||
alt="img"
|
||||
(click)="zoomImage(item)"
|
||||
(keyup.enter)="zoomImage(item)"
|
||||
tabindex="0"
|
||||
/>
|
||||
<button mat-icon-button class="overlay" (click)="deleteImage(item)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div class="img-container" class="flex flex-auto basis-1-5">
|
||||
<label ng-href for="fileUp">
|
||||
<img
|
||||
alt="add"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg=="
|
||||
/>
|
||||
<div class="attachments-section">
|
||||
<div class="attachments-header">
|
||||
<mat-icon class="attachments-header-icon">attach_file</mat-icon>
|
||||
<span class="attachments-title">Attachments</span>
|
||||
@if (form.files().value().length > 0) {
|
||||
<span class="attachments-badge">{{ form.files().value().length }}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="attachments-grid">
|
||||
@for (item of form.files().value(); track item) {
|
||||
<div class="attachment-card">
|
||||
<div
|
||||
class="thumbnail-wrapper"
|
||||
(click)="zoomImage(item)"
|
||||
(keyup.enter)="zoomImage(item)"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-label="View enlarged attachment"
|
||||
title="Click to preview"
|
||||
>
|
||||
<img [src]="item.thumbnail" alt="Attachment thumbnail" class="thumbnail-img" />
|
||||
<div class="zoom-overlay">
|
||||
<mat-icon class="zoom-icon">zoom_in</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="delete-btn"
|
||||
(click)="deleteImage(item); $event.stopPropagation()"
|
||||
aria-label="Delete attachment"
|
||||
title="Delete attachment"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<label
|
||||
for="fileUp"
|
||||
class="upload-tile"
|
||||
tabindex="0"
|
||||
(keydown.enter)="fileInput.click()"
|
||||
role="button"
|
||||
aria-label="Upload image attachments"
|
||||
>
|
||||
<div class="upload-content">
|
||||
<div class="upload-icon-circle">
|
||||
<mat-icon>add_photo_alternate</mat-icon>
|
||||
</div>
|
||||
<span class="upload-title">Add Image</span>
|
||||
<span class="upload-subtitle">Click to upload</span>
|
||||
</div>
|
||||
</label>
|
||||
<input type="file" id="fileUp" multiple accept="image/*" style="display: none" (change)="detectFiles($event)" />
|
||||
<input
|
||||
#fileInput
|
||||
type="file"
|
||||
id="fileUp"
|
||||
multiple
|
||||
accept="image/*"
|
||||
class="file-input-hidden"
|
||||
(change)="detectFiles($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -16,19 +16,242 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.img-container {
|
||||
/* Attachments Section */
|
||||
.attachments-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin: 6px 0 16px;
|
||||
}
|
||||
|
||||
.attachments-header {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--mat-sys-on-surface-variant, #64748b);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.attachments-header-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-primary, #0284c7);
|
||||
}
|
||||
|
||||
.attachments-badge {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 1px 7px;
|
||||
border-radius: 9999px;
|
||||
background: var(--mat-sys-secondary-container, #e2e8f0);
|
||||
color: var(--mat-sys-on-secondary-container, #1e293b);
|
||||
}
|
||||
|
||||
.attachments-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.attachment-card {
|
||||
position: relative;
|
||||
width: 110px;
|
||||
height: 155px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--mat-sys-outline-variant, #e2e8f0);
|
||||
background: var(--mat-sys-surface, #ffffff);
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.img-container .overlay {
|
||||
display: none;
|
||||
.attachment-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.img-container:hover > .overlay {
|
||||
display: inline-block;
|
||||
.thumbnail-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary, #0284c7);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.thumbnail-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:hover .thumbnail-img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.zoom-overlay {
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
top: 0;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:hover .zoom-overlay,
|
||||
.thumbnail-wrapper:focus-visible .zoom-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.zoom-icon {
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
min-width: 32px;
|
||||
padding: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
color: #dc2626;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease,
|
||||
transform 0.15s ease;
|
||||
z-index: 3;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.attachment-card:hover .delete-btn,
|
||||
.attachment-card:focus-within .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn:hover,
|
||||
.attachment-card .delete-btn:focus-visible {
|
||||
background: #dc2626;
|
||||
color: #ffffff;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.upload-tile {
|
||||
width: 110px;
|
||||
height: 155px;
|
||||
border-radius: 12px;
|
||||
border: 2px dashed var(--mat-sys-outline, #cbd5e1);
|
||||
background: var(--mat-sys-surface-container-low, #f8fafc);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-tile:hover {
|
||||
border-color: var(--mat-sys-primary, #0284c7);
|
||||
background: rgba(2, 132, 199, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.upload-tile:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary, #0284c7);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.upload-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.upload-icon-circle {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: var(--mat-sys-primary-container, #e0f2fe);
|
||||
color: var(--mat-sys-on-primary-container, #0284c7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-tile:hover .upload-icon-circle {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.upload-icon-circle mat-icon {
|
||||
font-size: 22px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.upload-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--mat-sys-on-surface, #1e293b);
|
||||
}
|
||||
|
||||
.upload-subtitle {
|
||||
font-size: 10px;
|
||||
color: var(--mat-sys-on-surface-variant, #64748b);
|
||||
}
|
||||
|
||||
.file-input-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.gold {
|
||||
|
||||
@@ -164,29 +164,70 @@
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
@for (item of form.files().value(); track item) {
|
||||
<div class="img-container" class="flex-auto basis-1-5">
|
||||
<img
|
||||
[src]="item.thumbnail"
|
||||
alt="img"
|
||||
(click)="zoomImage(item)"
|
||||
(keyup.enter)="zoomImage(item)"
|
||||
tabindex="0"
|
||||
/>
|
||||
<button mat-icon-button class="overlay" (click)="deleteImage(item)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div class="img-container" class="flex flex-auto basis-1-5">
|
||||
<label ng-href for="fileUp">
|
||||
<img
|
||||
alt="add"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg=="
|
||||
/>
|
||||
<div class="attachments-section">
|
||||
<div class="attachments-header">
|
||||
<mat-icon class="attachments-header-icon">attach_file</mat-icon>
|
||||
<span class="attachments-title">Attachments</span>
|
||||
@if (form.files().value().length > 0) {
|
||||
<span class="attachments-badge">{{ form.files().value().length }}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="attachments-grid">
|
||||
@for (item of form.files().value(); track item) {
|
||||
<div class="attachment-card">
|
||||
<div
|
||||
class="thumbnail-wrapper"
|
||||
(click)="zoomImage(item)"
|
||||
(keyup.enter)="zoomImage(item)"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-label="View enlarged attachment"
|
||||
title="Click to preview"
|
||||
>
|
||||
<img [src]="item.thumbnail" alt="Attachment thumbnail" class="thumbnail-img" />
|
||||
<div class="zoom-overlay">
|
||||
<mat-icon class="zoom-icon">zoom_in</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="delete-btn"
|
||||
(click)="deleteImage(item); $event.stopPropagation()"
|
||||
aria-label="Delete attachment"
|
||||
title="Delete attachment"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<label
|
||||
for="fileUp"
|
||||
class="upload-tile"
|
||||
tabindex="0"
|
||||
(keydown.enter)="fileInput.click()"
|
||||
role="button"
|
||||
aria-label="Upload image attachments"
|
||||
>
|
||||
<div class="upload-content">
|
||||
<div class="upload-icon-circle">
|
||||
<mat-icon>add_photo_alternate</mat-icon>
|
||||
</div>
|
||||
<span class="upload-title">Add Image</span>
|
||||
<span class="upload-subtitle">Click to upload</span>
|
||||
</div>
|
||||
</label>
|
||||
<input type="file" id="fileUp" multiple accept="image/*" style="display: none" (change)="detectFiles($event)" />
|
||||
<input
|
||||
#fileInput
|
||||
type="file"
|
||||
id="fileUp"
|
||||
multiple
|
||||
accept="image/*"
|
||||
class="file-input-hidden"
|
||||
(change)="detectFiles($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -16,19 +16,242 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.img-container {
|
||||
/* Attachments Section */
|
||||
.attachments-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin: 6px 0 16px;
|
||||
}
|
||||
|
||||
.attachments-header {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--mat-sys-on-surface-variant, #64748b);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.attachments-header-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-primary, #0284c7);
|
||||
}
|
||||
|
||||
.attachments-badge {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 1px 7px;
|
||||
border-radius: 9999px;
|
||||
background: var(--mat-sys-secondary-container, #e2e8f0);
|
||||
color: var(--mat-sys-on-secondary-container, #1e293b);
|
||||
}
|
||||
|
||||
.attachments-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.attachment-card {
|
||||
position: relative;
|
||||
width: 110px;
|
||||
height: 155px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--mat-sys-outline-variant, #e2e8f0);
|
||||
background: var(--mat-sys-surface, #ffffff);
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.img-container .overlay {
|
||||
display: none;
|
||||
.attachment-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.img-container:hover > .overlay {
|
||||
display: inline-block;
|
||||
.thumbnail-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary, #0284c7);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.thumbnail-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:hover .thumbnail-img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.zoom-overlay {
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
top: 0;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:hover .zoom-overlay,
|
||||
.thumbnail-wrapper:focus-visible .zoom-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.zoom-icon {
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
min-width: 32px;
|
||||
padding: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
color: #dc2626;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease,
|
||||
transform 0.15s ease;
|
||||
z-index: 3;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.attachment-card:hover .delete-btn,
|
||||
.attachment-card:focus-within .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn:hover,
|
||||
.attachment-card .delete-btn:focus-visible {
|
||||
background: #dc2626;
|
||||
color: #ffffff;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.upload-tile {
|
||||
width: 110px;
|
||||
height: 155px;
|
||||
border-radius: 12px;
|
||||
border: 2px dashed var(--mat-sys-outline, #cbd5e1);
|
||||
background: var(--mat-sys-surface-container-low, #f8fafc);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-tile:hover {
|
||||
border-color: var(--mat-sys-primary, #0284c7);
|
||||
background: rgba(2, 132, 199, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.upload-tile:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary, #0284c7);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.upload-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.upload-icon-circle {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: var(--mat-sys-primary-container, #e0f2fe);
|
||||
color: var(--mat-sys-on-primary-container, #0284c7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-tile:hover .upload-icon-circle {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.upload-icon-circle mat-icon {
|
||||
font-size: 22px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.upload-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--mat-sys-on-surface, #1e293b);
|
||||
}
|
||||
|
||||
.upload-subtitle {
|
||||
font-size: 10px;
|
||||
color: var(--mat-sys-on-surface-variant, #64748b);
|
||||
}
|
||||
|
||||
.file-input-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.gold {
|
||||
|
||||
@@ -184,29 +184,70 @@
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
@for (item of form.files().value(); track item) {
|
||||
<div class="img-container" class="flex-auto basis-1-5">
|
||||
<img
|
||||
[src]="item.thumbnail"
|
||||
alt="img"
|
||||
(click)="zoomImage(item)"
|
||||
(keyup.enter)="zoomImage(item)"
|
||||
tabindex="0"
|
||||
/>
|
||||
<button mat-icon-button class="overlay" (click)="deleteImage(item)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div class="img-container" class="flex flex-auto basis-1-5">
|
||||
<label ng-href for="fileUp">
|
||||
<img
|
||||
alt="add"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg=="
|
||||
/>
|
||||
<div class="attachments-section">
|
||||
<div class="attachments-header">
|
||||
<mat-icon class="attachments-header-icon">attach_file</mat-icon>
|
||||
<span class="attachments-title">Attachments</span>
|
||||
@if (form.files().value().length > 0) {
|
||||
<span class="attachments-badge">{{ form.files().value().length }}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="attachments-grid">
|
||||
@for (item of form.files().value(); track item) {
|
||||
<div class="attachment-card">
|
||||
<div
|
||||
class="thumbnail-wrapper"
|
||||
(click)="zoomImage(item)"
|
||||
(keyup.enter)="zoomImage(item)"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-label="View enlarged attachment"
|
||||
title="Click to preview"
|
||||
>
|
||||
<img [src]="item.thumbnail" alt="Attachment thumbnail" class="thumbnail-img" />
|
||||
<div class="zoom-overlay">
|
||||
<mat-icon class="zoom-icon">zoom_in</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="delete-btn"
|
||||
(click)="deleteImage(item); $event.stopPropagation()"
|
||||
aria-label="Delete attachment"
|
||||
title="Delete attachment"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<label
|
||||
for="fileUp"
|
||||
class="upload-tile"
|
||||
tabindex="0"
|
||||
(keydown.enter)="fileInput.click()"
|
||||
role="button"
|
||||
aria-label="Upload image attachments"
|
||||
>
|
||||
<div class="upload-content">
|
||||
<div class="upload-icon-circle">
|
||||
<mat-icon>add_photo_alternate</mat-icon>
|
||||
</div>
|
||||
<span class="upload-title">Add Image</span>
|
||||
<span class="upload-subtitle">Click to upload</span>
|
||||
</div>
|
||||
</label>
|
||||
<input type="file" id="fileUp" multiple accept="image/*" style="display: none" (change)="detectFiles($event)" />
|
||||
<input
|
||||
#fileInput
|
||||
type="file"
|
||||
id="fileUp"
|
||||
multiple
|
||||
accept="image/*"
|
||||
class="file-input-hidden"
|
||||
(change)="detectFiles($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -16,19 +16,242 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.img-container {
|
||||
/* Attachments Section */
|
||||
.attachments-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin: 6px 0 16px;
|
||||
}
|
||||
|
||||
.attachments-header {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--mat-sys-on-surface-variant, #64748b);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.attachments-header-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--mat-sys-primary, #0284c7);
|
||||
}
|
||||
|
||||
.attachments-badge {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 1px 7px;
|
||||
border-radius: 9999px;
|
||||
background: var(--mat-sys-secondary-container, #e2e8f0);
|
||||
color: var(--mat-sys-on-secondary-container, #1e293b);
|
||||
}
|
||||
|
||||
.attachments-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.attachment-card {
|
||||
position: relative;
|
||||
width: 110px;
|
||||
height: 155px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--mat-sys-outline-variant, #e2e8f0);
|
||||
background: var(--mat-sys-surface, #ffffff);
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.img-container .overlay {
|
||||
display: none;
|
||||
.attachment-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.img-container:hover > .overlay {
|
||||
display: inline-block;
|
||||
.thumbnail-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary, #0284c7);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.thumbnail-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:hover .thumbnail-img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.zoom-overlay {
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
top: 0;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.thumbnail-wrapper:hover .zoom-overlay,
|
||||
.thumbnail-wrapper:focus-visible .zoom-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.zoom-icon {
|
||||
color: #ffffff;
|
||||
font-size: 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
min-width: 32px;
|
||||
padding: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
color: #dc2626;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease,
|
||||
transform 0.15s ease;
|
||||
z-index: 3;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.attachment-card:hover .delete-btn,
|
||||
.attachment-card:focus-within .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn:hover,
|
||||
.attachment-card .delete-btn:focus-visible {
|
||||
background: #dc2626;
|
||||
color: #ffffff;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.attachment-card .delete-btn mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.upload-tile {
|
||||
width: 110px;
|
||||
height: 155px;
|
||||
border-radius: 12px;
|
||||
border: 2px dashed var(--mat-sys-outline, #cbd5e1);
|
||||
background: var(--mat-sys-surface-container-low, #f8fafc);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-tile:hover {
|
||||
border-color: var(--mat-sys-primary, #0284c7);
|
||||
background: rgba(2, 132, 199, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.upload-tile:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary, #0284c7);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.upload-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.upload-icon-circle {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: var(--mat-sys-primary-container, #e0f2fe);
|
||||
color: var(--mat-sys-on-primary-container, #0284c7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-tile:hover .upload-icon-circle {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.upload-icon-circle mat-icon {
|
||||
font-size: 22px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.upload-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--mat-sys-on-surface, #1e293b);
|
||||
}
|
||||
|
||||
.upload-subtitle {
|
||||
font-size: 10px;
|
||||
color: var(--mat-sys-on-surface-variant, #64748b);
|
||||
}
|
||||
|
||||
.file-input-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.gold {
|
||||
|
||||
@@ -134,29 +134,70 @@
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
@for (item of form.files().value(); track item) {
|
||||
<div class="img-container" class="flex-auto basis-1-5">
|
||||
<img
|
||||
[src]="item.thumbnail"
|
||||
alt="img"
|
||||
(click)="zoomImage(item)"
|
||||
(keyup.enter)="zoomImage(item)"
|
||||
tabindex="0"
|
||||
/>
|
||||
<button mat-icon-button class="overlay" (click)="deleteImage(item)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div class="img-container" class="flex flex-auto basis-1-5">
|
||||
<label ng-href for="fileUp">
|
||||
<img
|
||||
alt="add"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTUwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE1MCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjUwIiB5PSI3NSIgc3R5bGU9ImZpbGw6I2FhYTtmb250LXdlaWdodDpib2xkO2ZvbnQtc2l6ZToxMDBweDtmb250LWZhbWlseTpBcmlhbCxIZWx2ZXRpY2Esc2Fucy1zZXJpZjtkb21pbmFudC1iYXNlbGluZTpjZW50cmFsIj4rPC90ZXh0Pjwvc3ZnPg=="
|
||||
/>
|
||||
<div class="attachments-section">
|
||||
<div class="attachments-header">
|
||||
<mat-icon class="attachments-header-icon">attach_file</mat-icon>
|
||||
<span class="attachments-title">Attachments</span>
|
||||
@if (form.files().value().length > 0) {
|
||||
<span class="attachments-badge">{{ form.files().value().length }}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="attachments-grid">
|
||||
@for (item of form.files().value(); track item) {
|
||||
<div class="attachment-card">
|
||||
<div
|
||||
class="thumbnail-wrapper"
|
||||
(click)="zoomImage(item)"
|
||||
(keyup.enter)="zoomImage(item)"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-label="View enlarged attachment"
|
||||
title="Click to preview"
|
||||
>
|
||||
<img [src]="item.thumbnail" alt="Attachment thumbnail" class="thumbnail-img" />
|
||||
<div class="zoom-overlay">
|
||||
<mat-icon class="zoom-icon">zoom_in</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="delete-btn"
|
||||
(click)="deleteImage(item); $event.stopPropagation()"
|
||||
aria-label="Delete attachment"
|
||||
title="Delete attachment"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<label
|
||||
for="fileUp"
|
||||
class="upload-tile"
|
||||
tabindex="0"
|
||||
(keydown.enter)="fileInput.click()"
|
||||
role="button"
|
||||
aria-label="Upload image attachments"
|
||||
>
|
||||
<div class="upload-content">
|
||||
<div class="upload-icon-circle">
|
||||
<mat-icon>add_photo_alternate</mat-icon>
|
||||
</div>
|
||||
<span class="upload-title">Add Image</span>
|
||||
<span class="upload-subtitle">Click to upload</span>
|
||||
</div>
|
||||
</label>
|
||||
<input type="file" id="fileUp" multiple accept="image/*" style="display: none" (change)="detectFiles($event)" />
|
||||
<input
|
||||
#fileInput
|
||||
type="file"
|
||||
id="fileUp"
|
||||
multiple
|
||||
accept="image/*"
|
||||
class="file-input-hidden"
|
||||
(change)="detectFiles($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<img [src]="data" alt="image" (click)="close()" (keyup.enter)="close()" (keyup.space)="close()" tabindex="0" />
|
||||
<img [src]="data.resized" alt="image" (click)="close()" (keyup.enter)="close()" (keyup.space)="close()" tabindex="0" />
|
||||
|
||||
Reference in New Issue
Block a user