Employee leaving date fixed.
Image now showing in vouchers fixed.
This commit is contained in:
+77
-22
@@ -3,6 +3,9 @@ import multiprocessing
|
||||
import os
|
||||
|
||||
|
||||
# --- Worker sizing -----------------------------------------------------------
|
||||
# Prefer explicit WEB_CONCURRENCY in containers. Otherwise fall back to
|
||||
# WORKERS_PER_CORE * cpu_count(), with a minimum of 2 and optional MAX_WORKERS.
|
||||
workers_per_core_str = os.getenv("WORKERS_PER_CORE", "1")
|
||||
max_workers_str = os.getenv("MAX_WORKERS")
|
||||
use_max_workers = None
|
||||
@@ -10,12 +13,6 @@ if max_workers_str:
|
||||
use_max_workers = int(max_workers_str)
|
||||
web_concurrency_str = os.getenv("WEB_CONCURRENCY", None)
|
||||
|
||||
host = os.getenv("HOST", "0.0.0.0")
|
||||
port = os.getenv("PORT", "9994")
|
||||
bind_env = os.getenv("BIND", None)
|
||||
use_loglevel = os.getenv("LOG_LEVEL", "info")
|
||||
use_bind = bind_env if bind_env else f"{host}:{port}"
|
||||
|
||||
cores = multiprocessing.cpu_count()
|
||||
workers_per_core = float(workers_per_core_str)
|
||||
default_web_concurrency = workers_per_core * cores
|
||||
@@ -26,27 +23,53 @@ else:
|
||||
web_concurrency = max(int(default_web_concurrency), 2)
|
||||
if use_max_workers:
|
||||
web_concurrency = min(web_concurrency, use_max_workers)
|
||||
accesslog_var = os.getenv("ACCESS_LOG", "-")
|
||||
use_accesslog = accesslog_var or None
|
||||
errorlog_var = os.getenv("ERROR_LOG", "-")
|
||||
use_errorlog = errorlog_var or None
|
||||
graceful_timeout_str = os.getenv("GRACEFUL_TIMEOUT", "120")
|
||||
timeout_str = os.getenv("TIMEOUT", "120")
|
||||
keepalive_str = os.getenv("KEEP_ALIVE", "5")
|
||||
|
||||
# Gunicorn config variables
|
||||
loglevel = use_loglevel
|
||||
# --- Bind / logging ----------------------------------------------------------
|
||||
host = os.getenv("HOST", "0.0.0.0")
|
||||
port = os.getenv("PORT", "9994")
|
||||
bind_env = os.getenv("BIND", None)
|
||||
use_bind = bind_env if bind_env else f"{host}:{port}"
|
||||
|
||||
# Tell Gunicorn to trust X-Forwarded-For headers from the reverse proxy
|
||||
forwarded_allow_ips = "*"
|
||||
|
||||
# Optional: Ensure proxy protocol is recognized
|
||||
proxy_allow_ips = "*"
|
||||
|
||||
loglevel = os.getenv("LOG_LEVEL", "info")
|
||||
accesslog_var = os.getenv("ACCESS_LOG", "-")
|
||||
accesslog = accesslog_var or None
|
||||
errorlog_var = os.getenv("ERROR_LOG", "-")
|
||||
errorlog = errorlog_var or None
|
||||
|
||||
worker_tmp_dir = "/dev/shm"
|
||||
|
||||
# --- Timeouts / keepalive ----------------------------------------------------
|
||||
graceful_timeout = int(os.getenv("GRACEFUL_TIMEOUT", "120"))
|
||||
timeout = int(os.getenv("TIMEOUT", "120"))
|
||||
keepalive = int(os.getenv("KEEP_ALIVE", "5"))
|
||||
|
||||
# --- Robustness knobs (recommended) -----------------------------------------
|
||||
# Recycle workers gradually to mitigate memory leaks / fragmentation without
|
||||
# full container restarts. Tune via env if needed.
|
||||
max_requests = int(os.getenv("MAX_REQUESTS", "10000"))
|
||||
max_requests_jitter = int(os.getenv("MAX_REQUESTS_JITTER", "1000"))
|
||||
|
||||
# Prevent slow clients from holding connections forever (defaults are fine).
|
||||
# You can tune these via env if you ever need to.
|
||||
# worker_connections matters only for async worker types; kept here for clarity.
|
||||
worker_connections = int(os.getenv("WORKER_CONNECTIONS", "1000"))
|
||||
|
||||
# Helpful in containerized environments: ensure workers are responsive.
|
||||
# (Defaults are okay; leaving commented unless you want strict behavior.)
|
||||
# heartbeat_interval = int(os.getenv("HEARTBEAT_INTERVAL", "30"))
|
||||
|
||||
# --- Gunicorn config vars ----------------------------------------------------
|
||||
workers = web_concurrency
|
||||
bind = use_bind
|
||||
errorlog = use_errorlog
|
||||
worker_tmp_dir = "/dev/shm"
|
||||
accesslog = use_accesslog
|
||||
graceful_timeout = int(graceful_timeout_str)
|
||||
timeout = int(timeout_str)
|
||||
keepalive = int(keepalive_str)
|
||||
|
||||
|
||||
# For debugging and testing
|
||||
# --- Debug print (keep if you like) ------------------------------------------
|
||||
log_data = {
|
||||
"loglevel": loglevel,
|
||||
"workers": workers,
|
||||
@@ -56,10 +79,42 @@ log_data = {
|
||||
"keepalive": keepalive,
|
||||
"errorlog": errorlog,
|
||||
"accesslog": accesslog,
|
||||
"worker_tmp_dir": worker_tmp_dir,
|
||||
"max_requests": max_requests,
|
||||
"max_requests_jitter": max_requests_jitter,
|
||||
"worker_connections": worker_connections,
|
||||
# Additional, non-gunicorn variables
|
||||
"workers_per_core": workers_per_core,
|
||||
"use_max_workers": use_max_workers,
|
||||
"host": host,
|
||||
"port": port,
|
||||
"cores": cores,
|
||||
}
|
||||
print(json.dumps(log_data)) # noqa: T201
|
||||
|
||||
|
||||
# Variables Gunicorn reads
|
||||
# (these must be module-level names)
|
||||
# fmt: off
|
||||
# Core
|
||||
loglevel = loglevel
|
||||
workers = workers
|
||||
bind = bind
|
||||
|
||||
# Logging
|
||||
accesslog = accesslog
|
||||
errorlog = errorlog
|
||||
|
||||
# Runtime
|
||||
worker_tmp_dir = worker_tmp_dir
|
||||
graceful_timeout = graceful_timeout
|
||||
timeout = timeout
|
||||
keepalive = keepalive
|
||||
|
||||
# Recycling
|
||||
max_requests = max_requests
|
||||
max_requests_jitter = max_requests_jitter
|
||||
|
||||
# Concurrency (relevant for some worker types; harmless otherwise)
|
||||
worker_connections = worker_connections
|
||||
# fmt: on
|
||||
|
||||
@@ -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