update many things
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
TENANT=default
|
||||||
|
DIST_DIR=default
|
||||||
+16
-8
@@ -1,16 +1,15 @@
|
|||||||
# Build stage
|
# ---------- Build stage ----------
|
||||||
FROM node:20 AS builder
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
ARG TENANT=tis
|
ARG TENANT=tis
|
||||||
ARG DIST_DIR=tis
|
ARG DIST_DIR=tis
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# RUN npm config set registry "https://hub.megan.ir/npm/"
|
|
||||||
|
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
COPY package.json pnpm-lock.yaml ./
|
COPY package.json pnpm-lock.yaml ./
|
||||||
RUN pnpm install
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
@@ -20,14 +19,23 @@ RUN if [ "$TENANT" = "default" ]; then \
|
|||||||
pnpm run build:$TENANT; \
|
pnpm run build:$TENANT; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- Runtime stage ----------
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Remove default config
|
||||||
|
RUN rm -rf /etc/nginx/conf.d/*
|
||||||
|
|
||||||
|
# Copy optimized config
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
ARG DIST_DIR=tis
|
ARG DIST_DIR=tis
|
||||||
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
|
||||||
|
|
||||||
COPY --from=builder /app/dist/${DIST_DIR}/browser /usr/share/nginx/html
|
COPY --from=builder /app/dist/${DIST_DIR}/browser /usr/share/nginx/html
|
||||||
|
|
||||||
EXPOSE 5000
|
# Optional: reduce image size
|
||||||
|
# RUN apk add --no-cache brotli
|
||||||
|
|
||||||
|
EXPOSE 8090
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
+46
-1
@@ -10,6 +10,43 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular/build:application",
|
"builder": "@angular/build:application",
|
||||||
"configurations": {
|
"configurations": {
|
||||||
|
"default": {
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public-default"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"maximumError": "3MB",
|
||||||
|
"maximumWarning": "2MB",
|
||||||
|
"type": "initial"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximumError": "8kB",
|
||||||
|
"maximumWarning": "4kB",
|
||||||
|
"type": "anyComponentStyle"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.default.ts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"replace": "src/app.routes.ts",
|
||||||
|
"with": "src/tenants/default/app.routes.ts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"replace": "src/app/branding/branding.config.ts",
|
||||||
|
"with": "src/tenants/default/branding.config.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all",
|
||||||
|
"outputPath": "dist/default",
|
||||||
|
"serviceWorker": "ngsw-config.json"
|
||||||
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
@@ -32,10 +69,18 @@
|
|||||||
{
|
{
|
||||||
"replace": "src/environments/environment.ts",
|
"replace": "src/environments/environment.ts",
|
||||||
"with": "src/environments/environment.prod.ts"
|
"with": "src/environments/environment.prod.ts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"replace": "src/app.routes.ts",
|
||||||
|
"with": "src/tenants/default/app.routes.ts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"replace": "src/app/branding/branding.config.ts",
|
||||||
|
"with": "src/tenants/tis/branding.config.ts"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputHashing": "all",
|
"outputHashing": "all",
|
||||||
"outputPath": "dist/pos.client",
|
"outputPath": "dist/production",
|
||||||
"serviceWorker": "ngsw-config.json"
|
"serviceWorker": "ngsw-config.json"
|
||||||
},
|
},
|
||||||
"staging": {
|
"staging": {
|
||||||
|
|||||||
+40
-61
@@ -1,72 +1,51 @@
|
|||||||
version: "3.8"
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# Production service
|
app_tis:
|
||||||
app:
|
|
||||||
platform: ${DOCKER_PLATFORM:-linux/amd64}
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
TENANT: ${TENANT:-tis}
|
TENANT: tis
|
||||||
DIST_DIR: ${DIST_DIR:-tis}
|
DIST_DIR: tis
|
||||||
container_name: psp_panel_prod
|
|
||||||
ports:
|
|
||||||
- "5000:5000"
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:5000/" ]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 3
|
|
||||||
start_period: 5s
|
|
||||||
networks:
|
|
||||||
- psp_panel_network
|
|
||||||
# Staging service
|
|
||||||
# staging:
|
|
||||||
# build:
|
|
||||||
# context: .
|
|
||||||
# dockerfile: Dockerfile.staging
|
|
||||||
# container_name: psp_panel_staging
|
|
||||||
# ports:
|
|
||||||
# - "5050:5000"
|
|
||||||
# environment:
|
|
||||||
# - NODE_ENV=staging
|
|
||||||
# restart: unless-stopped
|
|
||||||
# healthcheck:
|
|
||||||
# test:
|
|
||||||
# ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"]
|
|
||||||
# interval: 30s
|
|
||||||
# timeout: 3s
|
|
||||||
# retries: 3
|
|
||||||
# start_period: 5s
|
|
||||||
# profiles:
|
|
||||||
# - staging
|
|
||||||
# networks:
|
|
||||||
# - psp_panel_network
|
|
||||||
|
|
||||||
# # Development service with hot reload
|
ports:
|
||||||
# dev:
|
- "8090:8090"
|
||||||
# build:
|
restart: unless-stopped
|
||||||
# context: .
|
|
||||||
# dockerfile: Dockerfile.dev
|
app_default:
|
||||||
# container_name: psp_panel-dev
|
build:
|
||||||
# working_dir: /app
|
context: .
|
||||||
# volumes:
|
dockerfile: Dockerfile
|
||||||
# - .:/app
|
args:
|
||||||
# - /app/node_modules
|
TENANT: default
|
||||||
# ports:
|
DIST_DIR: default
|
||||||
# - "4200:4200"
|
|
||||||
# environment:
|
ports:
|
||||||
# - NODE_ENV=development
|
- "8090:8090"
|
||||||
# restart: unless-stopped
|
restart: unless-stopped
|
||||||
# profiles:
|
|
||||||
# - dev
|
# labels:
|
||||||
# networks:
|
# - "traefik.enable=true"
|
||||||
# - psp_panel_network
|
|
||||||
|
# # 🌐 Router
|
||||||
|
# - "traefik.http.routers.psp.rule=Host(`your-domain.com`)"
|
||||||
|
# - "traefik.http.routers.psp.entrypoints=websecure"
|
||||||
|
# - "traefik.http.routers.psp.tls=true"
|
||||||
|
|
||||||
|
# # 🔐 SSL (auto with Traefik)
|
||||||
|
# - "traefik.http.routers.psp.tls.certresolver=letsencrypt"
|
||||||
|
|
||||||
|
# # ⚡ Service port (IMPORTANT)
|
||||||
|
# - "traefik.http.services.psp.loadbalancer.server.port=80"
|
||||||
|
|
||||||
|
# # 🚀 Compression at edge (optional)
|
||||||
|
# - "traefik.http.middlewares.compress.compress=true"
|
||||||
|
# - "traefik.http.routers.psp.middlewares=compress@docker"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
psp_panel_network:
|
web:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
+19
-71
@@ -1,80 +1,28 @@
|
|||||||
user nginx;
|
server {
|
||||||
worker_processes auto;
|
listen 8090;
|
||||||
error_log /var/log/nginx/error.log warn;
|
server_name _;
|
||||||
pid /var/run/nginx.pid;
|
|
||||||
|
|
||||||
events {
|
root /usr/share/nginx/html;
|
||||||
worker_connections 1024;
|
index index.html;
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
|
||||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log main;
|
|
||||||
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
tcp_nodelay on;
|
|
||||||
keepalive_timeout 65;
|
|
||||||
types_hash_max_size 2048;
|
|
||||||
client_max_body_size 20M;
|
|
||||||
|
|
||||||
|
# 🔥 Gzip
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_vary on;
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
gzip_proxied any;
|
|
||||||
gzip_comp_level 6;
|
gzip_comp_level 6;
|
||||||
gzip_types text/plain text/css text/xml text/javascript
|
|
||||||
application/json application/javascript application/xml+rss
|
|
||||||
application/rss+xml font/truetype font/opentype
|
|
||||||
application/vnd.ms-fontobject image/svg+xml;
|
|
||||||
|
|
||||||
include /etc/nginx/conf.d/*.conf;
|
# 🔥 Brotli (better than gzip)
|
||||||
|
# brotli on;
|
||||||
|
# brotli_comp_level 6;
|
||||||
|
# brotli_types text/plain text/css application/javascript application/json image/svg+xml;
|
||||||
|
|
||||||
server {
|
# 🔥 Cache static assets
|
||||||
listen 5000;
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||||
server_name localhost;
|
expires 1y;
|
||||||
charset utf-8;
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
root /usr/share/nginx/html;
|
# 🔥 Angular routing (SPA fallback)
|
||||||
index index.html index.htm;
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
# Security headers
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
||||||
|
|
||||||
# Cache control for static assets
|
|
||||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
|
||||||
expires 1y;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Service worker files should be revalidated on each request
|
|
||||||
location = /ngsw.json {
|
|
||||||
expires -1;
|
|
||||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /ngsw-worker.js {
|
|
||||||
expires -1;
|
|
||||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Angular routing - serve index.html for all routes
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Don't cache index.html
|
|
||||||
location = /index.html {
|
|
||||||
expires -1;
|
|
||||||
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
||||||
"appData": {
|
"appData": {
|
||||||
"appVersion": "0.0.0",
|
"appVersion": "0.0.0",
|
||||||
"buildDate": "2026-04-27T06:02:22.249Z"
|
"buildDate": "2026-05-03T16:17:50.761Z"
|
||||||
},
|
},
|
||||||
"assetGroups": [
|
"assetGroups": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ Displays: name, pos_type, serial_number, device, model, provider (6 fields - sam
|
|||||||
| `variant` | 'consumer' \| 'partner' \| 'full' | 'full' | Display variant mode |
|
| `variant` | 'consumer' \| 'partner' \| 'full' | 'full' | Display variant mode |
|
||||||
| `pos` | Signal<IPosEntity \| undefined> | undefined | POS entity data |
|
| `pos` | Signal<IPosEntity \| undefined> | undefined | POS entity data |
|
||||||
| `editMode` | Signal<boolean> | false | Edit mode state |
|
| `editMode` | Signal<boolean> | false | Edit mode state |
|
||||||
| `cardTitle` | string | 'اطلاعات پایانهی فروش' | Card header title |
|
| `cardTitle` | string | 'اطلاعات پایانه فروش' | Card header title |
|
||||||
| `showMoreActions` | boolean | true | Show more actions button |
|
| `showMoreActions` | boolean | true | Show more actions button |
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export class PosDisplayComponent {
|
|||||||
@Input() variant: PosVariant = 'full';
|
@Input() variant: PosVariant = 'full';
|
||||||
@Input() pos = signal<IPosEntity | undefined>(undefined);
|
@Input() pos = signal<IPosEntity | undefined>(undefined);
|
||||||
@Input() editMode = signal<boolean>(false);
|
@Input() editMode = signal<boolean>(false);
|
||||||
@Input() cardTitle = 'اطلاعات پایانهی فروش';
|
@Input() cardTitle = 'اطلاعات پایانه فروش';
|
||||||
@Input() showMoreActions = true;
|
@Input() showMoreActions = true;
|
||||||
|
|
||||||
@Output() editModeChange = new EventEmitter<boolean>();
|
@Output() editModeChange = new EventEmitter<boolean>();
|
||||||
|
|||||||
@@ -4,24 +4,32 @@
|
|||||||
<uikit-empty-state> </uikit-empty-state>
|
<uikit-empty-state> </uikit-empty-state>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<app-card-data cardTitle="اطلاعات فاکتور" [editable]="false">
|
<app-card-data cardTitle="اطلاعات فاکتور" [editable]="false" [backRoute]="backRoute">
|
||||||
<ng-template #moreActions>
|
<ng-template #moreActions>
|
||||||
<button pButton type="button" label="چاپ" icon="pi pi-print" outlined (click)="printInvoice()"></button>
|
<button
|
||||||
|
pButton
|
||||||
|
type="button"
|
||||||
|
label="چاپ"
|
||||||
|
icon="pi pi-print"
|
||||||
|
outlined
|
||||||
|
size="small"
|
||||||
|
(click)="printInvoice()"
|
||||||
|
></button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="grid grid-cols-3 gap-4 items-center">
|
<div class="listKeyValue">
|
||||||
<app-key-value label="کد رهگیری" [value]="invoice.code" />
|
<app-key-value label="کد رهگیری" [value]="invoice.code" />
|
||||||
<app-key-value label="تاریخ فاکتور" [value]="invoice.invoice_date" type="dateTime" />
|
<app-key-value label="تاریخ فاکتور" [value]="invoice.invoice_date" type="dateTime" />
|
||||||
<app-key-value label="تاریخ ایجاد فاکتور" [value]="invoice.created_at" type="dateTime" />
|
<app-key-value label="تاریخ ایجاد فاکتور" [value]="invoice.created_at" type="dateTime" />
|
||||||
<app-key-value label="وضعیت صدور به سامانهی مودیان">
|
<app-key-value label="وضعیت صدور به سامانهی مودیان">
|
||||||
<catalog-tax-provider-status-tag [status]="invoice.status.value" [translate]="invoice.status.translate" />
|
<catalog-tax-provider-status-tag [status]="invoice.status.value" [translate]="invoice.status.translate" />
|
||||||
</app-key-value>
|
</app-key-value>
|
||||||
<div class="col-span-3">
|
<div class="col-span-full">
|
||||||
<app-key-value label="توضیحات" [value]="invoice.notes" />
|
<app-key-value label="توضیحات" [value]="invoice.notes" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p-divider align="center"> اطلاعات پرداخت </p-divider>
|
<p-divider align="center"> اطلاعات پرداخت </p-divider>
|
||||||
<div class="grid grid-cols-3 gap-4 items-center">
|
<div class="grid md:grid-cols-3 sm:grid-cols-2 sm:gap-4 gap-3 items-center">
|
||||||
<app-key-value label="مجموع قابل پرداخت" [value]="invoice.total_amount" type="price" />
|
<app-key-value label="مجموع قابل پرداخت" [value]="invoice.total_amount" type="price" />
|
||||||
@for (payment of invoice.payments; track $index) {
|
@for (payment of invoice.payments; track $index) {
|
||||||
<app-key-value
|
<app-key-value
|
||||||
@@ -33,7 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p-divider align="center"> اطلاعات صادر کننده </p-divider>
|
<p-divider align="center"> اطلاعات صادر کننده </p-divider>
|
||||||
<div class="grid grid-cols-3 gap-4 items-center">
|
<div class="listKeyValue">
|
||||||
<app-key-value label="کسب و کار" [value]="invoice.pos!.complex.business_activity.name" />
|
<app-key-value label="کسب و کار" [value]="invoice.pos!.complex.business_activity.name" />
|
||||||
<app-key-value label="مجموعه" [value]="invoice.pos!.complex.name" />
|
<app-key-value label="مجموعه" [value]="invoice.pos!.complex.name" />
|
||||||
<app-key-value label="پایانهی فروش" [value]="invoice.pos!.name" />
|
<app-key-value label="پایانهی فروش" [value]="invoice.pos!.name" />
|
||||||
@@ -43,7 +51,7 @@
|
|||||||
@if (variant !== "customer") {
|
@if (variant !== "customer") {
|
||||||
<p-divider align="center"> اطلاعات مشتری </p-divider>
|
<p-divider align="center"> اطلاعات مشتری </p-divider>
|
||||||
@if (invoice.customer) {
|
@if (invoice.customer) {
|
||||||
<div class="grid grid-cols-3 gap-4 items-center">
|
<div class="listKeyValue">
|
||||||
@if (invoice.customer.type === "INDIVIDUAL") {
|
@if (invoice.customer.type === "INDIVIDUAL") {
|
||||||
<app-key-value label="نوع مشتری" value="حقیقی" />
|
<app-key-value label="نوع مشتری" value="حقیقی" />
|
||||||
<app-key-value label="نام" [value]="invoice.customer.individual?.first_name" />
|
<app-key-value label="نام" [value]="invoice.customer.individual?.first_name" />
|
||||||
@@ -60,7 +68,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} @else if (invoice.unknown_customer) {
|
} @else if (invoice.unknown_customer) {
|
||||||
<div class="grid grid-cols-3 gap-4 items-center">
|
<div class="listKeyValue">
|
||||||
<app-key-value label="نوع مشتری" value="نوع دوم" />
|
<app-key-value label="نوع مشتری" value="نوع دوم" />
|
||||||
<app-key-value label="عنوان" [value]="invoice.unknown_customer.name" />
|
<app-key-value label="عنوان" [value]="invoice.unknown_customer.name" />
|
||||||
<app-key-value label="کد ثبتی" [value]="invoice.unknown_customer.national_code" />
|
<app-key-value label="کد ثبتی" [value]="invoice.unknown_customer.national_code" />
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
TemplateRef,
|
TemplateRef,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { UrlTree } from '@angular/router';
|
||||||
import { ButtonDirective } from 'primeng/button';
|
import { ButtonDirective } from 'primeng/button';
|
||||||
import { Divider } from 'primeng/divider';
|
import { Divider } from 'primeng/divider';
|
||||||
import { TableModule } from 'primeng/table';
|
import { TableModule } from 'primeng/table';
|
||||||
@@ -48,6 +49,7 @@ export class ConsumerSaleInvoiceSharedComponent {
|
|||||||
@Input({ required: true }) loading!: boolean;
|
@Input({ required: true }) loading!: boolean;
|
||||||
@Input() invoice?: Maybe<ISaleInvoiceFullResponse>;
|
@Input() invoice?: Maybe<ISaleInvoiceFullResponse>;
|
||||||
@Input() variant: TConsumerSaleInvoice = 'full';
|
@Input() variant: TConsumerSaleInvoice = 'full';
|
||||||
|
@Input() backRoute?: UrlTree | string | any[];
|
||||||
|
|
||||||
@Output() onRefresh = new EventEmitter<void>();
|
@Output() onRefresh = new EventEmitter<void>();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ export class ConsumerBusinessActivityGoodsListComponent extends AbstractList<ICo
|
|||||||
@Input() header: IColumn[] = [
|
@Input() header: IColumn[] = [
|
||||||
{ field: 'id', header: 'شناسه', type: 'id' },
|
{ field: 'id', header: 'شناسه', type: 'id' },
|
||||||
{ field: 'name', header: 'عنوان' },
|
{ field: 'name', header: 'عنوان' },
|
||||||
{ field: 'sku', header: 'شناسهی عمومی' },
|
{ field: 'sku', header: 'شناسه عمومی' },
|
||||||
{
|
{
|
||||||
field: 'category',
|
field: 'category',
|
||||||
header: 'دستهبندی',
|
header: 'دستهبندی',
|
||||||
|
|||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<app-card-data cardTitle="اطلاعات پایانهی فروش" [editable]="true" [(editMode)]="editMode">
|
<app-card-data cardTitle="اطلاعات پایانه فروش" [editable]="true" [(editMode)]="editMode">
|
||||||
<ng-template #moreActions>
|
<ng-template #moreActions>
|
||||||
<p-button type="button" variant="outlined" (onClick)="toPosLanding()"> ورود به پایانه </p-button>
|
<p-button type="button" variant="outlined" size="small" (onClick)="toPosLanding()"> ورود به پایانه </p-button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="grid grid-cols-3 gap-4 items-center">
|
<div class="listKeyValue">
|
||||||
<app-key-value label="عنوان" [value]="pos()?.name" />
|
<app-key-value label="عنوان" [value]="pos()?.name" />
|
||||||
<app-key-value label="نوع پایانه" [value]="pos()?.pos_type" />
|
<app-key-value label="نوع پایانه" [value]="pos()?.pos_type" />
|
||||||
<app-key-value label="شماره سریال" [value]="pos()?.serial_number" />
|
<app-key-value label="شماره سریال" [value]="pos()?.serial_number" />
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<app-page-data-list
|
<app-page-data-list
|
||||||
pageTitle="آخرین فاکتورهای صادر شدهی امروز"
|
pageTitle="آخرین فاکتورهای صادر شده امروز"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
emptyPlaceholderTitle="تا به این لحظه فاکتوری صادر نشده است."
|
emptyPlaceholderTitle="تا به این لحظه فاکتوری صادر نشده است."
|
||||||
[items]="items()"
|
[items]="items()"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<app-card-data cardTitle="اطلاعات پایانهی فروش" [editable]="true" [(editMode)]="editMode">
|
<app-card-data cardTitle="اطلاعات پایانه فروش" [editable]="true" [(editMode)]="editMode">
|
||||||
<ng-template #moreActions>
|
<ng-template #moreActions>
|
||||||
<p-button type="button" variant="outlined" (onClick)="toPosLanding()"> ورود به پایانه </p-button>
|
<p-button type="button" variant="outlined" size="small" (onClick)="toPosLanding()"> ورود به پایانه </p-button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="grid grid-cols-3 gap-4 items-center">
|
<div class="listKeyValue">
|
||||||
<app-key-value label="عنوان" [value]="pos()?.name" />
|
<app-key-value label="عنوان" [value]="pos()?.name" />
|
||||||
<app-key-value label="نوع پایانه" [value]="pos()?.pos_type" />
|
<app-key-value label="نوع پایانه" [value]="pos()?.pos_type" />
|
||||||
<app-key-value label="شماره سریال" [value]="pos()?.serial_number" />
|
<app-key-value label="شماره سریال" [value]="pos()?.serial_number" />
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
name="starts_at"
|
name="starts_at"
|
||||||
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
||||||
/>
|
/>
|
||||||
<!-- <uikit-datepicker label="تاریخ انقضای لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
|
<!-- <uikit-datepicker label="تاریخ انقضا لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
|
||||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
||||||
</form>
|
</form>
|
||||||
</shared-dialog>
|
</shared-dialog>
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
<app-key-value label="عنوان" [value]="businessActivity()?.name" />
|
<app-key-value label="عنوان" [value]="businessActivity()?.name" />
|
||||||
<app-key-value label="کد اقتصادی" [value]="businessActivity()?.economic_code" />
|
<app-key-value label="کد اقتصادی" [value]="businessActivity()?.economic_code" />
|
||||||
<app-key-value label="صنف" [value]="businessActivity()?.guild?.name" />
|
<app-key-value label="صنف" [value]="businessActivity()?.guild?.name" />
|
||||||
<app-key-value label="تاریخ انقضای مجوز" [value]="businessActivity()?.license_info?.expires_at" type="date" />
|
<app-key-value label="تاریخ انقضا مجوز" [value]="businessActivity()?.license_info?.expires_at" type="date" />
|
||||||
<app-key-value label="تعداد کاربران مجاز" [value]="businessActivity()?.license_info?.accounts_limit" />
|
<app-key-value label="تعداد کاربران مجاز" [value]="businessActivity()?.license_info?.accounts_limit" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<app-card-data cardTitle="اطلاعات پایانهی فروش" [editable]="true" [(editMode)]="editMode">
|
<app-card-data cardTitle="اطلاعات پایانه فروش" [editable]="true" [(editMode)]="editMode">
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="grid grid-cols-3 gap-4 items-center">
|
<div class="grid grid-cols-3 gap-4 items-center">
|
||||||
<app-key-value label="عنوان" [value]="pos()?.name" />
|
<app-key-value label="عنوان" [value]="pos()?.name" />
|
||||||
|
|||||||
+5
-4
@@ -1,8 +1,9 @@
|
|||||||
<form [formGroup]="form" (submit)="submit()">
|
<form [formGroup]="form" (submit)="submit()">
|
||||||
<app-input [control]="form.controls.first_name" name="first_name" label="نام" />
|
<field-first-name [control]="form.controls.first_name" />
|
||||||
<app-input [control]="form.controls.last_name" name="last_name" label="نام خانوادگی" />
|
<field-last-name [control]="form.controls.last_name" />
|
||||||
<app-input [control]="form.controls.national_id" name="national_id" label="کد ملی" type="nationalId" />
|
<field-mobile-number [control]="form.controls.mobile_number" />
|
||||||
<app-input [control]="form.controls.economic_code" name="economic_code" label="کد اقتصادی" maxlength="11" />
|
<field-national-id [control]="form.controls.national_id" />
|
||||||
|
<field-economic-code [control]="form.controls.economic_code" />
|
||||||
|
|
||||||
<app-form-footer-actions submitLabel="ثبت مشتری و ادامه" (onCancel)="close()" (onSubmit)="submit()" />
|
<app-form-footer-actions submitLabel="ثبت مشتری و ادامه" (onCancel)="close()" (onSubmit)="submit()" />
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+26
-26
@@ -1,11 +1,16 @@
|
|||||||
import { postalCodeValidator } from '@/core/validators';
|
|
||||||
import { nationalIdValidator } from '@/core/validators/national-id.validator';
|
|
||||||
import { AbstractForm } from '@/shared/abstractClasses';
|
import { AbstractForm } from '@/shared/abstractClasses';
|
||||||
import { InputComponent } from '@/shared/components';
|
import {
|
||||||
|
EconomicCodeComponent,
|
||||||
|
FirstNameComponent,
|
||||||
|
LastNameComponent,
|
||||||
|
MobileNumberComponent,
|
||||||
|
NationalIdComponent,
|
||||||
|
} from '@/shared/components';
|
||||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||||
|
import { fieldControl } from '@/shared/constants';
|
||||||
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
||||||
import { Component, inject } from '@angular/core';
|
import { Component, computed, inject } from '@angular/core';
|
||||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { IIndividualCustomer } from '../../../models/customer';
|
import { IIndividualCustomer } from '../../../models/customer';
|
||||||
import { PosLandingStore } from '../../../store/main.store';
|
import { PosLandingStore } from '../../../store/main.store';
|
||||||
@@ -13,36 +18,31 @@ import { PosLandingStore } from '../../../store/main.store';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'pos-customer-individual-form',
|
selector: 'pos-customer-individual-form',
|
||||||
templateUrl: './form.component.html',
|
templateUrl: './form.component.html',
|
||||||
imports: [ReactiveFormsModule, InputComponent, FormFooterActionsComponent],
|
imports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormFooterActionsComponent,
|
||||||
|
FirstNameComponent,
|
||||||
|
LastNameComponent,
|
||||||
|
NationalIdComponent,
|
||||||
|
EconomicCodeComponent,
|
||||||
|
MobileNumberComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class CustomerIndividualFormComponent extends AbstractForm<
|
export class CustomerIndividualFormComponent extends AbstractForm<
|
||||||
IIndividualCustomer,
|
IIndividualCustomer,
|
||||||
IIndividualCustomer
|
IIndividualCustomer
|
||||||
> {
|
> {
|
||||||
private readonly store = inject(PosLandingStore);
|
private readonly store = inject(PosLandingStore);
|
||||||
|
costumerInfo = computed(() => this.store.customer().info?.customer_individual);
|
||||||
|
|
||||||
override showSuccessMessage = false;
|
override showSuccessMessage = false;
|
||||||
form = this.fb.group({
|
form = this.fb.group({
|
||||||
first_name: [
|
first_name: fieldControl.first_name(this.costumerInfo()?.first_name || ''),
|
||||||
this.store.customer().info?.customer_individual?.first_name || '',
|
last_name: fieldControl.last_name(this.costumerInfo()?.last_name || ''),
|
||||||
[Validators.required],
|
national_id: fieldControl.national_id(this.costumerInfo()?.national_id || ''),
|
||||||
],
|
postal_code: fieldControl.postal_code(this.costumerInfo()?.postal_code || ''),
|
||||||
last_name: [
|
economic_code: fieldControl.economic_code(this.costumerInfo()?.economic_code || ''),
|
||||||
this.store.customer().info?.customer_individual?.last_name || '',
|
mobile_number: fieldControl.mobile_number(this.costumerInfo()?.mobile_number || ''),
|
||||||
[Validators.required],
|
|
||||||
],
|
|
||||||
national_id: [
|
|
||||||
this.store.customer().info?.customer_individual?.national_id || '',
|
|
||||||
[Validators.required, nationalIdValidator()],
|
|
||||||
],
|
|
||||||
economic_code: [
|
|
||||||
this.store.customer().info?.customer_individual?.economic_code || '',
|
|
||||||
[Validators.required],
|
|
||||||
],
|
|
||||||
postal_code: [
|
|
||||||
this.store.customer().info?.customer_individual?.postal_code || '',
|
|
||||||
[postalCodeValidator()],
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
override submitForm() {
|
override submitForm() {
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<form [formGroup]="form" (submit)="submit()">
|
<form [formGroup]="form" (submit)="submit()">
|
||||||
|
<app-input [control]="form.controls.name" name="name" label="نام مشتری" />
|
||||||
<app-input [control]="form.controls.national_id" name="national_id" label="شماره ملی" type="nationalId" />
|
<app-input [control]="form.controls.national_id" name="national_id" label="شماره ملی" type="nationalId" />
|
||||||
<app-input [control]="form.controls.name" name="name" label="نام" />
|
|
||||||
<app-form-footer-actions submitLabel="ثبت بدون اطلاعات خریدار و ادامه" (onCancel)="close()" />
|
<app-form-footer-actions submitLabel="ثبت بدون اطلاعات خریدار و ادامه" (onCancel)="close()" />
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<button
|
<button
|
||||||
pButton
|
pButton
|
||||||
type="button"
|
type="button"
|
||||||
icon="pi pi-refresh"
|
icon="pi pi-trash"
|
||||||
outlined
|
outlined
|
||||||
severity="danger"
|
severity="danger"
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -75,6 +75,15 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
|
|||||||
this.updateCalculateAmount(value as any);
|
this.updateCalculateAmount(value as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
form.controls.payload.controls.profit_amount.valueChanges.subscribe((value) => {
|
||||||
|
// if ((form.controls.discount_amount.value || 0) > (value || 0)) {
|
||||||
|
form.controls.discount_amount.setValue(0);
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
form.controls.payload.controls.profit_percentage.valueChanges.subscribe((value) => {
|
||||||
|
form.controls.discount_amount.setValue(0);
|
||||||
|
});
|
||||||
|
|
||||||
form.setValue({
|
form.setValue({
|
||||||
unit_price: 200_000_000,
|
unit_price: 200_000_000,
|
||||||
quantity: 2,
|
quantity: 2,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export interface IIndividualCustomer {
|
|||||||
last_name: string;
|
last_name: string;
|
||||||
national_id: string;
|
national_id: string;
|
||||||
postal_code: string;
|
postal_code: string;
|
||||||
|
mobile_number: string;
|
||||||
economic_code: string;
|
economic_code: string;
|
||||||
is_favorite?: boolean;
|
is_favorite?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
<p-button
|
<p-button
|
||||||
icon="pi pi-filter"
|
icon="pi pi-filter"
|
||||||
type="button"
|
type="button"
|
||||||
|
size="small"
|
||||||
[outlined]="!activeFilters().length"
|
[outlined]="!activeFilters().length"
|
||||||
(click)="toggleFilterVisible()"
|
(click)="toggleFilterVisible()"
|
||||||
></p-button>
|
></p-button>
|
||||||
<button pButton icon="pi pi-refresh" type="button" outlined (click)="refresh()"></button>
|
<button pButton icon="pi pi-refresh" type="button" size="small" outlined (click)="refresh()"></button>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-inner-pages-header>
|
</app-inner-pages-header>
|
||||||
|
|||||||
+26
-2
@@ -14,19 +14,43 @@
|
|||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="flex items-center justify-center gap-4">
|
<div class="flex items-center justify-center gap-4">
|
||||||
|
@if (["success", "failure"].includes(saleInvoice.status.value.toLowerCase())) {
|
||||||
|
<p-button
|
||||||
|
label="ابطال"
|
||||||
|
type="button"
|
||||||
|
severity="danger"
|
||||||
|
(click)="revokeInvoice()"
|
||||||
|
[loading]="revokingInvoiceLoading()"
|
||||||
|
[disabled]="onAction()"
|
||||||
|
></p-button>
|
||||||
|
}
|
||||||
@if (saleInvoice.status.value.toLowerCase() === "not_send") {
|
@if (saleInvoice.status.value.toLowerCase() === "not_send") {
|
||||||
<p-button label="ارسال فاکتور" type="button" (click)="sendInvoice()" [loading]="sendingLoading()"></p-button>
|
<p-button
|
||||||
|
label="ارسال فاکتور"
|
||||||
|
type="button"
|
||||||
|
(click)="sendInvoice()"
|
||||||
|
[loading]="sendingLoading()"
|
||||||
|
[disabled]="onAction()"
|
||||||
|
></p-button>
|
||||||
}
|
}
|
||||||
@if (saleInvoice.status.value.toLowerCase() === "queued") {
|
@if (saleInvoice.status.value.toLowerCase() === "queued") {
|
||||||
<p-button
|
<p-button
|
||||||
label="استعلام وضعیت ارسال"
|
label="استعلام وضعیت ارسال"
|
||||||
type="button"
|
type="button"
|
||||||
|
severity="info"
|
||||||
(click)="getStatus()"
|
(click)="getStatus()"
|
||||||
[loading]="gettingStatusLoading()"
|
[loading]="gettingStatusLoading()"
|
||||||
|
[disabled]="onAction()"
|
||||||
></p-button>
|
></p-button>
|
||||||
}
|
}
|
||||||
@if (saleInvoice.status.value.toLowerCase() === "failure") {
|
@if (saleInvoice.status.value.toLowerCase() === "failure") {
|
||||||
<p-button label="ارسال فاکتور" type="button" (click)="resendInvoice()" [loading]="resendingLoading()"></p-button>
|
<p-button
|
||||||
|
label="ارسال مجدد"
|
||||||
|
type="button"
|
||||||
|
(click)="resendInvoice()"
|
||||||
|
[loading]="resendingLoading()"
|
||||||
|
[disabled]="onAction()"
|
||||||
|
></p-button>
|
||||||
}
|
}
|
||||||
<a [routerLink]="singlePageRoute()" pButton type="button" (click)="viewDetails()" outlined>مشاهده جزییات</a>
|
<a [routerLink]="singlePageRoute()" pButton type="button" (click)="viewDetails()" outlined>مشاهده جزییات</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Component, computed, EventEmitter, inject, Input, Output, signal } from
|
|||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { Button, ButtonDirective } from 'primeng/button';
|
import { Button, ButtonDirective } from 'primeng/button';
|
||||||
import { Card } from 'primeng/card';
|
import { Card } from 'primeng/card';
|
||||||
import { Tag } from 'primeng/tag';
|
|
||||||
import { finalize } from 'rxjs';
|
import { finalize } from 'rxjs';
|
||||||
import { posSaleInvoicesNamedRoutes } from '../constants';
|
import { posSaleInvoicesNamedRoutes } from '../constants';
|
||||||
import { IPosSaleInvoicesSummaryResponse } from '../models';
|
import { IPosSaleInvoicesSummaryResponse } from '../models';
|
||||||
@@ -17,7 +16,6 @@ import { PosSaleInvoicesService } from '../services/main.service';
|
|||||||
imports: [
|
imports: [
|
||||||
Button,
|
Button,
|
||||||
KeyValueComponent,
|
KeyValueComponent,
|
||||||
Tag,
|
|
||||||
Card,
|
Card,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
ButtonDirective,
|
ButtonDirective,
|
||||||
@@ -34,6 +32,15 @@ export class SaleInvoiceCardComponent {
|
|||||||
sendingLoading = signal(false);
|
sendingLoading = signal(false);
|
||||||
gettingStatusLoading = signal(false);
|
gettingStatusLoading = signal(false);
|
||||||
resendingLoading = signal(false);
|
resendingLoading = signal(false);
|
||||||
|
revokingInvoiceLoading = signal(false);
|
||||||
|
|
||||||
|
onAction = computed(
|
||||||
|
() =>
|
||||||
|
this.sendingLoading() ||
|
||||||
|
this.gettingStatusLoading() ||
|
||||||
|
this.resendingLoading() ||
|
||||||
|
this.revokingInvoiceLoading(),
|
||||||
|
);
|
||||||
|
|
||||||
singlePageRoute = computed(() =>
|
singlePageRoute = computed(() =>
|
||||||
posSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(this.saleInvoice?.id),
|
posSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(this.saleInvoice?.id),
|
||||||
@@ -74,9 +81,6 @@ export class SaleInvoiceCardComponent {
|
|||||||
.getInquiry(this.saleInvoice.id)
|
.getInquiry(this.saleInvoice.id)
|
||||||
.pipe(finalize(() => this.gettingStatusLoading.set(false)))
|
.pipe(finalize(() => this.gettingStatusLoading.set(false)))
|
||||||
.subscribe((res) => {
|
.subscribe((res) => {
|
||||||
this.toastService.info({
|
|
||||||
text: `وضعیت: ${res.status || 'نامشخص'}`,
|
|
||||||
});
|
|
||||||
this.refreshRequested.emit();
|
this.refreshRequested.emit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -91,5 +95,16 @@ export class SaleInvoiceCardComponent {
|
|||||||
this.refreshRequested.emit();
|
this.refreshRequested.emit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
revokeInvoice() {
|
||||||
|
this.revokingInvoiceLoading.set(true);
|
||||||
|
this.service
|
||||||
|
.revoke(this.saleInvoice.id)
|
||||||
|
.pipe(finalize(() => this.revokingInvoiceLoading.set(false)))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.toastService.success({ text: 'ابطال فاکتور با موفقیت انجام شد.' });
|
||||||
|
this.refreshRequested.emit();
|
||||||
|
});
|
||||||
|
}
|
||||||
viewDetails() {}
|
viewDetails() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export const POS_SALE_INVOICES_API_ROUTES = {
|
|||||||
retry: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/retry`,
|
retry: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/retry`,
|
||||||
status: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/status`,
|
status: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/status`,
|
||||||
getInquiry: (invoiceId: string) => `${baseUrl()}/${invoiceId}/inquiry`,
|
getInquiry: (invoiceId: string) => `${baseUrl()}/${invoiceId}/inquiry`,
|
||||||
|
revoke: (invoiceId: string) => `${baseUrl()}/${invoiceId}/revoke`,
|
||||||
attempts: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/attempts`,
|
attempts: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/attempts`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import ISummary from '@/core/models/summary';
|
import ISummary from '@/core/models/summary';
|
||||||
|
import { TspProviderResponseStatus } from '@/shared/catalog';
|
||||||
import { IEnumTranslate } from '@/shared/models/enum_translate.type';
|
import { IEnumTranslate } from '@/shared/models/enum_translate.type';
|
||||||
|
|
||||||
export interface IPosSaleInvoicesRawResponse {
|
export interface IPosSaleInvoicesRawResponse {
|
||||||
|
|||||||
@@ -59,6 +59,13 @@ export class PosSaleInvoicesService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
revoke(invoiceId: string): Observable<IPosSaleInvoiceFiscalStatusResponse> {
|
||||||
|
return this.http.post<IPosSaleInvoiceFiscalStatusResponse>(
|
||||||
|
this.apiRoutes.fiscal.revoke(invoiceId),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getFiscalAttempts(invoiceId: string): Observable<IPosSaleInvoiceFiscalAttemptsResponse> {
|
getFiscalAttempts(invoiceId: string): Observable<IPosSaleInvoiceFiscalAttemptsResponse> {
|
||||||
return this.http.get<IPosSaleInvoiceFiscalAttemptsResponse>(
|
return this.http.get<IPosSaleInvoiceFiscalAttemptsResponse>(
|
||||||
this.apiRoutes.fiscal.attempts(invoiceId),
|
this.apiRoutes.fiscal.attempts(invoiceId),
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
<consumer-saleInvoice-shared [loading]="loading()" [invoice]="invoice()" variant="pos" />
|
<div class="p-4">
|
||||||
|
<consumer-saleInvoice-shared [loading]="loading()" [invoice]="invoice()" [backRoute]="backRoute()" variant="pos" />
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ConsumerSaleInvoiceSharedComponent } from '@/domains/consumer/component
|
|||||||
import pageParamsUtils from '@/utils/page-params.utils';
|
import pageParamsUtils from '@/utils/page-params.utils';
|
||||||
import { Component, computed, inject, signal } from '@angular/core';
|
import { Component, computed, inject, signal } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { posSaleInvoicesNamedRoutes } from '../constants';
|
||||||
import { PosSaleInvoiceStore } from '../store/main.store';
|
import { PosSaleInvoiceStore } from '../store/main.store';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -19,6 +20,8 @@ export class PosSaleInvoiceComponent {
|
|||||||
readonly invoice = computed(() => this.store.entity());
|
readonly invoice = computed(() => this.store.entity());
|
||||||
readonly loading = computed(() => this.store.loading());
|
readonly loading = computed(() => this.store.loading());
|
||||||
|
|
||||||
|
readonly backRoute = computed(() => posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!());
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.store.getData(this.invoiceId());
|
this.store.getData(this.invoiceId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const SUPER_ADMIN_MENU_ITEMS = [
|
|||||||
routerLink: ['/super_admin/guilds'],
|
routerLink: ['/super_admin/guilds'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'شناسهی کالاها',
|
label: 'شناسه کالاها',
|
||||||
icon: 'pi pi-fw pi-good',
|
icon: 'pi pi-fw pi-good',
|
||||||
routerLink: [stockKeepingUnitsNamedRoutes.stockKeepingUnits.meta.pagePath!()],
|
routerLink: [stockKeepingUnitsNamedRoutes.stockKeepingUnits.meta.pagePath!()],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
name="license.starts_at"
|
name="license.starts_at"
|
||||||
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
||||||
/> -->
|
/> -->
|
||||||
<!-- <uikit-datepicker label="تاریخ انقضای لایسنس" [control]="form.controls.license.controls.expires_at" name="license.expires_at" /> -->
|
<!-- <uikit-datepicker label="تاریخ انقضا لایسنس" [control]="form.controls.license.controls.expires_at" name="license.expires_at" /> -->
|
||||||
<!-- <app-partner-select label="شریک تجاری" [control]="form.controls.license.controls.partner_id" /> -->
|
<!-- <app-partner-select label="شریک تجاری" [control]="form.controls.license.controls.partner_id" /> -->
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
name="starts_at"
|
name="starts_at"
|
||||||
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
||||||
/>
|
/>
|
||||||
<!-- <uikit-datepicker label="تاریخ انقضای لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
|
<!-- <uikit-datepicker label="تاریخ انقضا لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
|
||||||
<app-partner-select label="شریک تجاری" [control]="form.controls.partner_id" [showClear]="true" />
|
<app-partner-select label="شریک تجاری" [control]="form.controls.partner_id" [showClear]="true" />
|
||||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export class ConsumersComponent extends AbstractList<IAdminConsumerResponse> {
|
|||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// field: 'license_expires_at',
|
// field: 'license_expires_at',
|
||||||
// header: 'تاریخ انقضای لایسنس',
|
// header: 'تاریخ انقضا لایسنس',
|
||||||
// customDataModel(item) {
|
// customDataModel(item) {
|
||||||
// if (item.license_info) {
|
// if (item.license_info) {
|
||||||
// return formatJalali(item.license_info.expires_at);
|
// return formatJalali(item.license_info.expires_at);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<app-card-data cardTitle="اطلاعات پایانهی فروش" [editable]="true" [(editMode)]="editMode">
|
<app-card-data cardTitle="اطلاعات پایانه فروش" [editable]="true" [(editMode)]="editMode">
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="grid grid-cols-3 gap-4 items-center">
|
<div class="grid grid-cols-3 gap-4 items-center">
|
||||||
<app-key-value label="عنوان" [value]="pos()?.name" />
|
<app-key-value label="عنوان" [value]="pos()?.name" />
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<app-input label="عنوان" [control]="form.controls.name" name="name" />
|
<app-input label="عنوان" [control]="form.controls.name" name="name" />
|
||||||
<field-sku label="شناسهی کالا" [control]="form.controls.sku_id" name="sku_id" />
|
<field-sku label="شناسه کالا" [control]="form.controls.sku_id" name="sku_id" />
|
||||||
<admin-guild-categories-select
|
<admin-guild-categories-select
|
||||||
[guildId]="guildId"
|
[guildId]="guildId"
|
||||||
label="دستهبندی"
|
label="دستهبندی"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class GuildGoodsListComponent extends AbstractList<IGuildGoodsResponse> {
|
|||||||
@Input() header: IColumn[] = [
|
@Input() header: IColumn[] = [
|
||||||
{ field: 'image_url', header: 'تصویر', type: 'thumbnail' },
|
{ field: 'image_url', header: 'تصویر', type: 'thumbnail' },
|
||||||
{ field: 'name', header: 'عنوان' },
|
{ field: 'name', header: 'عنوان' },
|
||||||
{ field: 'sku', header: 'شناسهی کالا', type: 'nested', nestedOption: { path: 'sku.name' } },
|
{ field: 'sku', header: 'شناسه کالا', type: 'nested', nestedOption: { path: 'sku.name' } },
|
||||||
{
|
{
|
||||||
field: 'category',
|
field: 'category',
|
||||||
header: 'دستهبندی',
|
header: 'دستهبندی',
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<app-page-data-list
|
<app-page-data-list
|
||||||
pageTitle="گزارش شارژهای ثبت شدهی کاربر"
|
pageTitle="لیست شارژ کاربر"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
emptyPlaceholderTitle="تا به حال شارژ کاربری انجام نشده است"
|
emptyPlaceholderTitle="تا به حال شارژ کاربری انجام نشده است"
|
||||||
[items]="items()"
|
[items]="items()"
|
||||||
|
|||||||
+4
-3
@@ -17,15 +17,16 @@ export class AdminPartnerChargeAccountListComponent extends AbstractList<IAdminP
|
|||||||
override setColumns(): void {
|
override setColumns(): void {
|
||||||
this.columns = [
|
this.columns = [
|
||||||
{ field: 'tracking_code', header: 'کد پیگیری' },
|
{ field: 'tracking_code', header: 'کد پیگیری' },
|
||||||
{ field: 'charged_license_count', header: 'تعداد شارژ کاربر' },
|
{ field: 'charged_license_count', header: 'تعداد شارژ کاربر', type: 'number' },
|
||||||
{ field: 'remained_license_count', header: 'شارژ باقی مانده' },
|
{ field: 'remained_license_count', header: 'شارژ باقی مانده', type: 'number' },
|
||||||
{
|
{
|
||||||
field: 'activation_count',
|
field: 'activation_count',
|
||||||
header: 'مقدار مصرف شده',
|
header: 'مقدار مصرف شده',
|
||||||
|
type: 'number',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'activation_expires_at',
|
field: 'activation_expires_at',
|
||||||
header: 'تاریخ انقضای فروش',
|
header: 'تاریخ انقضا فروش',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
dateOption: {
|
dateOption: {
|
||||||
expiredMode: true,
|
expiredMode: true,
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<app-page-data-list
|
<app-page-data-list
|
||||||
pageTitle="گزارش شارژهای ثبت شدهی لایسنسها"
|
pageTitle="لیست شارژ لایسنس"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
emptyPlaceholderTitle="تا به حال شارژ لایسنسی انجام نشده است"
|
emptyPlaceholderTitle="تا به حال شارژ لایسنسی انجام نشده است"
|
||||||
[items]="items()"
|
[items]="items()"
|
||||||
|
|||||||
+5
-4
@@ -18,15 +18,16 @@ export class AdminPartnerChargeLicenseTransactionListComponent extends AbstractL
|
|||||||
override setColumns(): void {
|
override setColumns(): void {
|
||||||
this.columns = [
|
this.columns = [
|
||||||
{ field: 'tracking_code', header: 'کد پیگیری' },
|
{ field: 'tracking_code', header: 'کد پیگیری' },
|
||||||
{ field: 'charged_license_count', header: 'لایسنسهای خریداری شده' },
|
{ field: 'charged_license_count', header: 'لایسنس خریداری شده', type: 'number' },
|
||||||
{ field: 'remained_license_count', header: 'لایسنسهای باقیمانده' },
|
{ field: 'remained_license_count', header: 'لایسنس باقیمانده', type: 'number' },
|
||||||
{
|
{
|
||||||
field: 'activation_count',
|
field: 'activation_count',
|
||||||
header: 'لایسنسهای فروخته شده',
|
header: 'لایسنس فروخته شده',
|
||||||
|
type: 'number',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'activation_expires_at',
|
field: 'activation_expires_at',
|
||||||
header: 'تاریخ انقضای فروش',
|
header: 'تاریخ انقضا فروش',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
dateOption: {
|
dateOption: {
|
||||||
expiredMode: true,
|
expiredMode: true,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<app-page-data-list
|
<app-page-data-list
|
||||||
pageTitle="لیست لایسنسهای فروخته شده"
|
pageTitle="لیست لایسنس فروخته شده"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
emptyPlaceholderTitle="تا به حال لایسنسی فروخته نشده است"
|
emptyPlaceholderTitle="تا به حال لایسنسی فروخته نشده است"
|
||||||
[items]="items()"
|
[items]="items()"
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ export class AdminPartnerLicensesComponent extends AbstractList<IPartnerActivate
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'license',
|
field: 'license',
|
||||||
header: 'تعداد کاربر قابل تعریف',
|
header: 'تعداد کاربر مجاز',
|
||||||
type: 'nested',
|
type: 'nested',
|
||||||
nestedOption: { path: 'license.accounts_limit' },
|
nestedOption: { path: 'license.accounts_limit', type: 'number' },
|
||||||
},
|
},
|
||||||
{ field: 'expires_at', header: 'تاریخ انقضای لایسنس', type: 'date' },
|
{ field: 'expires_at', header: 'تاریخ انقضا لایسنس', type: 'date' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class PartnersComponent extends AbstractList<IPartnerResponse> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'license_renew',
|
field: 'license_renew',
|
||||||
header: 'تعداد لایسنسهای تمدیدی',
|
header: 'تعداد لایسنس تمدیدی',
|
||||||
customDataModel: this.licensesRenewStatus,
|
customDataModel: this.licensesRenewStatus,
|
||||||
},
|
},
|
||||||
{ field: 'status', header: 'وضعیت' },
|
{ field: 'status', header: 'وضعیت' },
|
||||||
|
|||||||
@@ -8,12 +8,52 @@
|
|||||||
<div class="grid grid-cols-3 gap-4 items-center">
|
<div class="grid grid-cols-3 gap-4 items-center">
|
||||||
<app-key-value label="عنوان شریک تجاری" [value]="partner()?.name" />
|
<app-key-value label="عنوان شریک تجاری" [value]="partner()?.name" />
|
||||||
<app-key-value label="کد شریک تجاری" [value]="partner()?.code" />
|
<app-key-value label="کد شریک تجاری" [value]="partner()?.code" />
|
||||||
|
|
||||||
|
<div class="col-span-3 grid grid-cols-3 gap-4 mt-6">
|
||||||
|
<partner-license-info-template
|
||||||
|
title="لایسنسها"
|
||||||
|
[total]="partner()?.licenses_status?.total || 0"
|
||||||
|
[expired]="partner()?.licenses_status?.expired || 0"
|
||||||
|
[activated]="partner()?.licenses_status?.used || 0"
|
||||||
|
[remained]="
|
||||||
|
(partner()?.licenses_status?.total || 0) -
|
||||||
|
(partner()?.licenses_status?.used || 0) -
|
||||||
|
(partner()?.licenses_status?.expired || 0)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<partner-license-info-template
|
||||||
|
title="شارژ کاربران"
|
||||||
|
[total]="partner()?.account_quota_status?.total || 0"
|
||||||
|
[expired]="partner()?.account_quota_status?.expired || 0"
|
||||||
|
[activated]="partner()?.account_quota_status?.used || 0"
|
||||||
|
[remained]="
|
||||||
|
(partner()?.account_quota_status?.total || 0) -
|
||||||
|
(partner()?.account_quota_status?.used || 0) -
|
||||||
|
(partner()?.account_quota_status?.expired || 0)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<partner-license-info-template
|
||||||
|
title="تمدید لایسنسها"
|
||||||
|
[total]="partner()?.license_renew_status?.total || 0"
|
||||||
|
[expired]="partner()?.license_renew_status?.expired || 0"
|
||||||
|
[activated]="partner()?.license_renew_status?.used || 0"
|
||||||
|
[remained]="
|
||||||
|
(partner()?.license_renew_status?.total || 0) -
|
||||||
|
(partner()?.license_renew_status?.used || 0) -
|
||||||
|
(partner()?.license_renew_status?.expired || 0)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!--
|
||||||
<p-divider align="center" class="col-span-3">اطلاعات لایسنسها</p-divider>
|
<p-divider align="center" class="col-span-3">اطلاعات لایسنسها</p-divider>
|
||||||
<app-key-value label="مجموع لایسنسهای خریداری شده" [value]="partner()?.licenses_status?.total" />
|
<app-key-value type="number" label="مجموع لایسنس خریداری شده" [value]="partner()?.licenses_status?.total" />
|
||||||
<app-key-value label="مجموع لایسنسهای منقضی شده" [value]="partner()?.licenses_status?.expired" />
|
<app-key-value type="number" label="مجموع لایسنس منقضی شده" [value]="partner()?.licenses_status?.expired" />
|
||||||
<app-key-value label="مجموع لایسنسهای فروخته شده" [value]="partner()?.licenses_status?.used" />
|
<app-key-value type="number" label="مجموع لایسنس فروخته شده" [value]="partner()?.licenses_status?.used" />
|
||||||
<app-key-value
|
<app-key-value
|
||||||
label="مجموع لایسنسهای باقیمانده"
|
type="number"
|
||||||
|
label="مجموع لایسنس باقیمانده"
|
||||||
[value]="
|
[value]="
|
||||||
(partner()?.licenses_status?.total || 0) -
|
(partner()?.licenses_status?.total || 0) -
|
||||||
(partner()?.licenses_status?.used || 0) -
|
(partner()?.licenses_status?.used || 0) -
|
||||||
@@ -21,10 +61,19 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<p-divider align="center" class="col-span-3">اطلاعات شارژ کاربران</p-divider>
|
<p-divider align="center" class="col-span-3">اطلاعات شارژ کاربران</p-divider>
|
||||||
<app-key-value label="مجموع کاربران خریداری شده" [value]="partner()?.account_quota_status?.total" />
|
|
||||||
<app-key-value label="مجموع کاربران منقضی شده" [value]="partner()?.account_quota_status?.expired" />
|
|
||||||
<app-key-value label="مجموع کاربران فروخته شده" [value]="partner()?.account_quota_status?.used" />
|
|
||||||
<app-key-value
|
<app-key-value
|
||||||
|
type="number"
|
||||||
|
label="مجموع کاربران خریداری شده"
|
||||||
|
[value]="partner()?.account_quota_status?.total"
|
||||||
|
/>
|
||||||
|
<app-key-value
|
||||||
|
type="number"
|
||||||
|
label="مجموع کاربران منقضی شده"
|
||||||
|
[value]="partner()?.account_quota_status?.expired"
|
||||||
|
/>
|
||||||
|
<app-key-value type="number" label="مجموع کاربران فروخته شده" [value]="partner()?.account_quota_status?.used" />
|
||||||
|
<app-key-value
|
||||||
|
type="number"
|
||||||
label="مجموع کاربران باقیمانده"
|
label="مجموع کاربران باقیمانده"
|
||||||
[value]="
|
[value]="
|
||||||
(partner()?.account_quota_status?.total || 0) -
|
(partner()?.account_quota_status?.total || 0) -
|
||||||
@@ -33,17 +82,18 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<p-divider align="center" class="col-span-3">اطلاعات تمدید لایسنسها</p-divider>
|
<p-divider align="center" class="col-span-3">اطلاعات تمدید لایسنسها</p-divider>
|
||||||
<app-key-value label="مجموع تمدید خریداری شده" [value]="partner()?.license_renew_status?.total" />
|
<app-key-value type="number" label="مجموع تمدید خریداری شده" [value]="partner()?.license_renew_status?.total" />
|
||||||
<app-key-value label="مجموع تمدید منقضی شده" [value]="partner()?.license_renew_status?.expired" />
|
<app-key-value type="number" label="مجموع تمدید منقضی شده" [value]="partner()?.license_renew_status?.expired" />
|
||||||
<app-key-value label="مجموع تمدید فروخته شده" [value]="partner()?.license_renew_status?.used" />
|
<app-key-value type="number" label="مجموع تمدید فروخته شده" [value]="partner()?.license_renew_status?.used" />
|
||||||
<app-key-value
|
<app-key-value
|
||||||
|
type="number"
|
||||||
label="مجموع تمدید باقیمانده"
|
label="مجموع تمدید باقیمانده"
|
||||||
[value]="
|
[value]="
|
||||||
(partner()?.license_renew_status?.total || 0) -
|
(partner()?.license_renew_status?.total || 0) -
|
||||||
(partner()?.license_renew_status?.used || 0) -
|
(partner()?.license_renew_status?.used || 0) -
|
||||||
(partner()?.license_renew_status?.expired || 0)
|
(partner()?.license_renew_status?.expired || 0)
|
||||||
"
|
"
|
||||||
/>
|
/>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</app-card-data>
|
</app-card-data>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { BreadcrumbService } from '@/core/services';
|
import { BreadcrumbService } from '@/core/services';
|
||||||
|
import { PartnerLicenseInfoTemplateComponent } from '@/domains/partner/modules/dashboard/components/licenseInfo/license-info-template.component';
|
||||||
import { AppCardComponent, KeyValueComponent } from '@/shared/components';
|
import { AppCardComponent, KeyValueComponent } from '@/shared/components';
|
||||||
import { Component, computed, effect, inject, signal } from '@angular/core';
|
import { Component, computed, effect, inject, signal } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Button } from 'primeng/button';
|
import { Button } from 'primeng/button';
|
||||||
import { Divider } from 'primeng/divider';
|
|
||||||
import { ConsumerAccountListComponent } from '../components/accounts/list.component';
|
import { ConsumerAccountListComponent } from '../components/accounts/list.component';
|
||||||
import { PartnerChargeLicenseFormDialogComponent } from '../components/charge-license-form-dialog.component';
|
import { PartnerChargeLicenseFormDialogComponent } from '../components/charge-license-form-dialog.component';
|
||||||
import { AdminPartnerChargeAccountFormDialogComponent } from '../components/chargeAccount/charge-account-form-dialog.component';
|
import { AdminPartnerChargeAccountFormDialogComponent } from '../components/chargeAccount/charge-account-form-dialog.component';
|
||||||
@@ -28,7 +28,7 @@ import { PartnerStore } from '../store/partner.store';
|
|||||||
PartnerChargeLicenseFormDialogComponent,
|
PartnerChargeLicenseFormDialogComponent,
|
||||||
AdminPartnerLicensesComponent,
|
AdminPartnerLicensesComponent,
|
||||||
AdminPartnerChargeLicenseTransactionListComponent,
|
AdminPartnerChargeLicenseTransactionListComponent,
|
||||||
Divider,
|
PartnerLicenseInfoTemplateComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PartnerComponent {
|
export class PartnerComponent {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<app-page-data-list
|
<app-page-data-list
|
||||||
[pageTitle]="'مدیریت شناسهی کالاها'"
|
[pageTitle]="'مدیریت شناسه کالاها'"
|
||||||
[addNewCtaLabel]="'افزودن شناسهی کالا'"
|
[addNewCtaLabel]="'افزودن شناسه کالا'"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
[showAdd]="true"
|
[showAdd]="true"
|
||||||
emptyPlaceholderTitle="شناسهی کالایی یافت نشد"
|
emptyPlaceholderTitle="شناسه کالایی یافت نشد"
|
||||||
emptyPlaceholderDescription="برای افزودن شناسهی کالا، روی دکمهٔ بالا کلیک کنید."
|
emptyPlaceholderDescription="برای افزودن شناسه کالا، روی دکمهٔ بالا کلیک کنید."
|
||||||
[items]="items()"
|
[items]="items()"
|
||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
[showDetails]="false"
|
[showDetails]="false"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<i class="pi pi-shopping-cart"></i>
|
<i class="pi pi-shopping-cart"></i>
|
||||||
<span class="text-base font-bold">سفارشها</span>
|
<span class="text-base font-bold">سفارشها</span>
|
||||||
</div>
|
</div>
|
||||||
<button pButton type="button" icon="pi pi-refresh" outlined size="small" (click)="clearOrderList()">
|
<button pButton type="button" icon="pi pi-trash" outlined size="small" (click)="clearOrderList()">
|
||||||
پاک کردن سفارشها
|
پاک کردن سفارشها
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
[options]="items()"
|
[options]="items()"
|
||||||
optionLabel="name"
|
optionLabel="name"
|
||||||
[optionValue]="selectOptionValue"
|
[optionValue]="selectOptionValue"
|
||||||
placeholder="انتخاب شناسهی کالا"
|
placeholder="انتخاب شناسه کالا"
|
||||||
[formControl]="control"
|
[formControl]="control"
|
||||||
[showClear]="showClear"
|
[showClear]="showClear"
|
||||||
[name]="name || 'sku_id'"
|
[name]="name || 'sku_id'"
|
||||||
|
|||||||
+11
-13
@@ -1,20 +1,20 @@
|
|||||||
<uikit-field
|
<uikit-field
|
||||||
[label]="preparedLabel()"
|
[label]="label"
|
||||||
[name]="name"
|
[name]="name"
|
||||||
[control]="selectedType.value === 'amount' ? amountControl : percentageControl"
|
[control]="selectedType.value === 'amount' ? amountControl : percentageControl"
|
||||||
[showLabel]="!!label"
|
[showLabel]="!!label"
|
||||||
[showErrors]="showErrors"
|
[showErrors]="showErrors"
|
||||||
>
|
>
|
||||||
@if (labelSuffix) {
|
<ng-template #labelView>
|
||||||
<ng-template #labelView>
|
<div class="flex gap-1 items-center justify-between">
|
||||||
<div class="flex gap-1 items-center">
|
<uikit-label [name]="name">
|
||||||
<uikit-label [name]="name">
|
{{ preparedLabel() }}
|
||||||
{{ preparedLabel() }}
|
</uikit-label>
|
||||||
</uikit-label>
|
@if (labelSuffix) {
|
||||||
<ng-container [ngTemplateOutlet]="labelSuffix"></ng-container>
|
<ng-container [ngTemplateOutlet]="labelSuffix"></ng-container>
|
||||||
</div>
|
}
|
||||||
</ng-template>
|
</div>
|
||||||
}
|
</ng-template>
|
||||||
|
|
||||||
<p-inputgroup>
|
<p-inputgroup>
|
||||||
@if (selectedType.value === "amount") {
|
@if (selectedType.value === "amount") {
|
||||||
@@ -24,9 +24,7 @@
|
|||||||
[attr.name]="name"
|
[attr.name]="name"
|
||||||
[attr.placeholder]="placeholder"
|
[attr.placeholder]="placeholder"
|
||||||
[attr.autocomplete]="autocomplete"
|
[attr.autocomplete]="autocomplete"
|
||||||
[class]="
|
[class]="` w-full hasSuffix text-left ${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`"
|
||||||
` w-full hasSuffix text-left ${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`
|
|
||||||
"
|
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
[required]="required"
|
[required]="required"
|
||||||
[invalid]="amountControl.invalid && (amountControl.touched || amountControl.dirty)"
|
[invalid]="amountControl.invalid && (amountControl.touched || amountControl.dirty)"
|
||||||
|
|||||||
+4
-4
@@ -128,12 +128,12 @@ export class AmountPercentageInputComponent {
|
|||||||
if (notValid) {
|
if (notValid) {
|
||||||
if (minValidator) {
|
if (minValidator) {
|
||||||
this.toastService.warn({
|
this.toastService.warn({
|
||||||
text: `مقدار فیلد باید بیشتر از ${min} باشد.`,
|
text: `مقدار ${this.label} باید بیشتر از ${formatWithCurrency(min)} باشد.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (maxValidator) {
|
if (maxValidator) {
|
||||||
this.toastService.warn({
|
this.toastService.warn({
|
||||||
text: `مقدار فیلد باید کمتر از ${max} باشد.`,
|
text: `مقدار ${this.label} باید کمتر از ${formatWithCurrency(max)} باشد.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,12 +146,12 @@ export class AmountPercentageInputComponent {
|
|||||||
if (isPercentageType) {
|
if (isPercentageType) {
|
||||||
const amountValue = (this.baseAmount * newValue) / 100;
|
const amountValue = (this.baseAmount * newValue) / 100;
|
||||||
newValueToSet = newValue + '';
|
newValueToSet = newValue + '';
|
||||||
this.amountControl.setValue(amountValue);
|
this.amountControl.setValue(isNaN(amountValue) ? 0 : amountValue);
|
||||||
if (notValid) {
|
if (notValid) {
|
||||||
this.percentageControl.setValue(newValue);
|
this.percentageControl.setValue(newValue);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const percentageValue = (newValue / this.baseAmount) * 100;
|
const percentageValue = ((newValue / this.baseAmount) * 100).toFixed(2);
|
||||||
newValueToSet = newValue + '';
|
newValueToSet = newValue + '';
|
||||||
this.percentageControl.setValue(percentageValue);
|
this.percentageControl.setValue(percentageValue);
|
||||||
this.preparedLabel.set(`${this.label} (${percentageValue} درصد)`);
|
this.preparedLabel.set(`${this.label} (${percentageValue} درصد)`);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<p-breadcrumb [model]="items()"></p-breadcrumb>
|
<p-breadcrumb [model]="items()"></p-breadcrumb>
|
||||||
<div class="absolute left-0 inset-y-0">
|
<div class="absolute left-0 inset-y-0">
|
||||||
<div class="me-4 flex items-center justify-center h-full">
|
<div class="me-4 flex items-center justify-center h-full">
|
||||||
<i class="pi pi-question-circle" pTooltip="محتوای آموزشی" tooltipPosition="bottom"></i>
|
<i class="pi pi-question-circle" pTooltip="راهنما" tooltipPosition="bottom"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,18 +2,24 @@
|
|||||||
<ng-template #title>
|
<ng-template #title>
|
||||||
<ng-container [ngTemplateOutlet]="header">
|
<ng-container [ngTemplateOutlet]="header">
|
||||||
<div class="flex items-center gap-10 justify-between">
|
<div class="flex items-center gap-10 justify-between">
|
||||||
<h5 class="font-bold py-1.5">{{ cardTitle }}</h5>
|
<div class="flex items-center gap-2">
|
||||||
|
@if (backRoute) {
|
||||||
|
<p-button icon="pi pi-arrow-right" aria-label="بازگشت" size="small" outlined [routerLink]="backRoute" />
|
||||||
|
}
|
||||||
|
<h5 class="font-bold py-1.5">{{ cardTitle }}</h5>
|
||||||
|
</div>
|
||||||
<div class="flex items-center gap-2 shrink-0">
|
<div class="flex items-center gap-2 shrink-0">
|
||||||
<ng-container [ngTemplateOutlet]="moreActions"></ng-container>
|
<ng-container [ngTemplateOutlet]="moreActions"></ng-container>
|
||||||
@if (showRefresh) {
|
@if (showRefresh) {
|
||||||
<p-button icon="pi pi-refresh" outlined (click)="refresh()"></p-button>
|
<p-button icon="pi pi-refresh" outlined size="small" (click)="refresh()"></p-button>
|
||||||
}
|
}
|
||||||
@if (editable) {
|
@if (editable) {
|
||||||
<button
|
<button
|
||||||
pButton
|
pButton
|
||||||
type="button"
|
type="button"
|
||||||
[icon]="`pi ${editMode ? 'pi-times' : 'pi-pencil'}`"
|
[icon]="`pi ${editMode ? 'pi-times' : 'pi-pencil'}`"
|
||||||
class="p-button-text p-button-plain"
|
outlined
|
||||||
|
size="small"
|
||||||
(click)="onEditClick()"
|
(click)="onEditClick()"
|
||||||
></button>
|
></button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
signal,
|
signal,
|
||||||
TemplateRef,
|
TemplateRef,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { RouterLink, UrlTree } from '@angular/router';
|
||||||
import { Button, ButtonDirective } from 'primeng/button';
|
import { Button, ButtonDirective } from 'primeng/button';
|
||||||
import { Card } from 'primeng/card';
|
import { Card } from 'primeng/card';
|
||||||
|
|
||||||
@@ -16,12 +17,13 @@ import { Card } from 'primeng/card';
|
|||||||
selector: 'app-card-data',
|
selector: 'app-card-data',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
templateUrl: './card-data.component.html',
|
templateUrl: './card-data.component.html',
|
||||||
imports: [Card, CommonModule, ButtonDirective, Button],
|
imports: [Card, CommonModule, ButtonDirective, Button, RouterLink],
|
||||||
})
|
})
|
||||||
export class AppCardComponent {
|
export class AppCardComponent {
|
||||||
@Input() cardTitle!: string;
|
@Input() cardTitle!: string;
|
||||||
@Input() editable: boolean = false;
|
@Input() editable: boolean = false;
|
||||||
@Input() showRefresh: boolean = false;
|
@Input() showRefresh: boolean = false;
|
||||||
|
@Input() backRoute?: UrlTree | string | any[];
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set editMode(v: boolean) {
|
set editMode(v: boolean) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'field-sku',
|
selector: 'field-sku',
|
||||||
template: `<catalog-sku-select label="شناسهی کالا" [control]="control" [name]="name" />`,
|
template: `<catalog-sku-select label="شناسه کالا" [control]="control" [name]="name" />`,
|
||||||
imports: [ReactiveFormsModule, CatalogSkuSelectComponent],
|
imports: [ReactiveFormsModule, CatalogSkuSelectComponent],
|
||||||
})
|
})
|
||||||
export class SkuComponent {
|
export class SkuComponent {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="grow">
|
<div class="grow">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@if (backRoute) {
|
@if (backRoute) {
|
||||||
<p-button icon="pi pi-arrow-right" aria-label="بازگشت" outlined [routerLink]="backRoute" />
|
<p-button icon="pi pi-arrow-right" aria-label="بازگشت" outlined size="small" [routerLink]="backRoute" />
|
||||||
}
|
}
|
||||||
<span class="text-xl font-bold">{{ pageTitle }}</span>
|
<span class="text-xl font-bold">{{ pageTitle }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="inline-flex gap-2 items-center w-full">
|
<div class="inline-flex gap-2 items-center w-full">
|
||||||
<span class="text-muted-color text-base font-normal flex-auto grow-0">{{ label }}:</span>
|
<span class="text-muted-color text-base font-normal flex-auto grow-0">{{ label }}:</span>
|
||||||
<div
|
<div
|
||||||
class="shrink-0 grow"
|
class="shrink-0 grow max-sm:text-left"
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'text-right': alignment === 'start',
|
'text-right': alignment === 'start',
|
||||||
'text-center': alignment === 'center',
|
'text-center': alignment === 'center',
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export class KeyValueComponent {
|
|||||||
| 'thumbnail'
|
| 'thumbnail'
|
||||||
| 'nested'
|
| 'nested'
|
||||||
| 'index'
|
| 'index'
|
||||||
|
| 'number'
|
||||||
| 'simple' = 'simple';
|
| 'simple' = 'simple';
|
||||||
|
|
||||||
@Input() trueValueToShow?: string;
|
@Input() trueValueToShow?: string;
|
||||||
@@ -89,6 +90,9 @@ export class KeyValueComponent {
|
|||||||
case 'has':
|
case 'has':
|
||||||
value = 'ندارد';
|
value = 'ندارد';
|
||||||
break;
|
break;
|
||||||
|
case 'number':
|
||||||
|
value = '0';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<div class="h-full overflow-auto">
|
||||||
|
<div
|
||||||
|
[ngClass]="{
|
||||||
|
'px-4': captionBox,
|
||||||
|
'pb-4': true,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
@if (captionBox) {
|
||||||
|
<div class="pt-4">
|
||||||
|
<ng-container [ngTemplateOutlet]="captionBox"></ng-container>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
}
|
||||||
|
<div class="grid grid-cols-1 gap-3" [ngClass]="{ 'pt-4': captionBox }">
|
||||||
|
@for (item of items; track `gridView_${$index}`) {
|
||||||
|
<div class="card border border-surface-border bg-surface-50! mb-0! rounded-2xl p-4!">
|
||||||
|
<div class="listKeyValue">
|
||||||
|
@for (col of columns; track `gridView_${col.field.toString()}_${$index}`) {
|
||||||
|
@if (col.type !== "index") {
|
||||||
|
<app-key-value [label]="col.header">
|
||||||
|
@if (col.type === "thumbnail") {
|
||||||
|
<div
|
||||||
|
class="w-20 h-20 rounded-2xl overflow-hidden bg-surface-50 cursor-pointer"
|
||||||
|
(click)="openThumbnailModal(item)"
|
||||||
|
>
|
||||||
|
@if (item && col?.field && item[col!.field!]) {
|
||||||
|
<img [src]="item[col.field]" class="w-full h-full object-cover" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
} @else if (getTemplate(col)) {
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="getTemplate(col)"
|
||||||
|
[ngTemplateOutletContext]="{ $implicit: item }"
|
||||||
|
></ng-container>
|
||||||
|
} @else if (col.canCopy) {
|
||||||
|
<uikit-copy [text]="getCell(item, col)"></uikit-copy>
|
||||||
|
} @else {
|
||||||
|
<span [class]="getPreviewClasses(item, col)">
|
||||||
|
{{ getCell(item, col) }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</app-key-value>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (showEdit || showDelete || showDetails) {
|
||||||
|
<hr />
|
||||||
|
<div class="flex justify-center gap-2 mt-2">
|
||||||
|
@if (showEdit) {
|
||||||
|
<p-button icon="pi pi-pencil" label="ویرایش" size="small" (click)="edit(item)"> </p-button>
|
||||||
|
}
|
||||||
|
@if (showDelete) {
|
||||||
|
<p-button icon="pi pi-trash" label="حذف" size="small" (click)="remove(item)"> </p-button>
|
||||||
|
}
|
||||||
|
@if (showDetails) {
|
||||||
|
<p-button icon="pi pi-eye" label="مشاهده" size="small" (click)="details(item)"> </p-button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (loading) {
|
||||||
|
@for (i of [1, 2, 3, 4]; track `grid_view_loading${$index}`) {
|
||||||
|
<div class="w-full h-40">
|
||||||
|
<p-skeleton height="100%"></p-skeleton>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (showPaginator) {
|
||||||
|
<ng-container [ngTemplateOutlet]="paginator"> </ng-container>
|
||||||
|
}
|
||||||
|
@if (items.length === 0 && !loading) {
|
||||||
|
<ng-container [ngTemplateOutlet]="emptyMessageCard"></ng-container>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { UikitCopyComponent } from '@/uikit';
|
||||||
|
import { formatJalali, formatWithCurrency, jalaliDiff } from '@/utils';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
computed,
|
||||||
|
ContentChild,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
signal,
|
||||||
|
TemplateRef,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { DrawerModule } from 'primeng/drawer';
|
||||||
|
import { PaginatorModule } from 'primeng/paginator';
|
||||||
|
import { SkeletonModule } from 'primeng/skeleton';
|
||||||
|
import { TableModule } from 'primeng/table';
|
||||||
|
import { KeyValueComponent } from '../key-value.component/key-value.component';
|
||||||
|
|
||||||
|
type TDataType =
|
||||||
|
| 'simple'
|
||||||
|
| 'price'
|
||||||
|
| 'boolean'
|
||||||
|
| 'date'
|
||||||
|
| 'nested'
|
||||||
|
| 'index'
|
||||||
|
| 'id'
|
||||||
|
| 'thumbnail'
|
||||||
|
| 'number';
|
||||||
|
export interface IColumn<T = any> {
|
||||||
|
field: T extends object ? keyof T | string : string;
|
||||||
|
header: string;
|
||||||
|
width?: string;
|
||||||
|
minWidth?: string;
|
||||||
|
canCopy?: boolean;
|
||||||
|
type?: TDataType;
|
||||||
|
|
||||||
|
nestedOption?: {
|
||||||
|
path: string;
|
||||||
|
type?: TDataType;
|
||||||
|
};
|
||||||
|
thumbnailOptions?: {
|
||||||
|
editable: boolean;
|
||||||
|
deletable: boolean;
|
||||||
|
showPreview: boolean;
|
||||||
|
};
|
||||||
|
dateOption?: {
|
||||||
|
expiredMode?: boolean;
|
||||||
|
dateTime?: boolean;
|
||||||
|
onlyTime?: boolean;
|
||||||
|
};
|
||||||
|
customDataModel?: TemplateRef<any> | ((item: T) => string | number | boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-page-data-list-grid-view',
|
||||||
|
templateUrl: './page-data-list-grid-view.component.html',
|
||||||
|
host: {
|
||||||
|
class: 'block w-full h-full overflow-hidden',
|
||||||
|
},
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TableModule,
|
||||||
|
ButtonModule,
|
||||||
|
SkeletonModule,
|
||||||
|
PaginatorModule,
|
||||||
|
DrawerModule,
|
||||||
|
UikitCopyComponent,
|
||||||
|
KeyValueComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppPageDataListGridView<I = any> {
|
||||||
|
@Input({ required: true }) pageTitle!: string;
|
||||||
|
@Input({ required: true }) columns!: IColumn[];
|
||||||
|
@Input({ required: true }) items!: any[];
|
||||||
|
@Input({ required: true }) loading!: boolean;
|
||||||
|
@Input({ required: true }) emptyPlaceholderTitle!: string;
|
||||||
|
@Input() addNewCtaLabel?: string;
|
||||||
|
@Input() emptyPlaceholderDescription?: string = '';
|
||||||
|
@Input() emptyPlaceholderCtaLabel?: string;
|
||||||
|
@Input() currentPage?: number = 1;
|
||||||
|
@Input() showEdit: boolean = false;
|
||||||
|
@Input() showDelete: boolean = false;
|
||||||
|
@Input() showDetails: boolean = false;
|
||||||
|
@Input() showAdd: boolean = false;
|
||||||
|
@Input() showRefresh: boolean = true;
|
||||||
|
@Input() isFiltered: boolean = false;
|
||||||
|
@Input() fullHeight?: boolean = false;
|
||||||
|
@Input() height: string = '';
|
||||||
|
@Input() expandable?: boolean = false;
|
||||||
|
@Input() expandableItemKey?: string = '';
|
||||||
|
@Input() expandColumns?: Maybe<IColumn[]> = null;
|
||||||
|
@Input() dataKey?: string = 'items';
|
||||||
|
@Input() showPaginator?: boolean;
|
||||||
|
|
||||||
|
@ContentChild('filter', { static: true }) filter!: TemplateRef<any> | null;
|
||||||
|
@ContentChild('moreActions', { static: true }) moreActions!: TemplateRef<any> | null;
|
||||||
|
@ContentChild('expandableTemp', { static: false }) expandableTemp!: TemplateRef<any> | null;
|
||||||
|
@ContentChild('paginator', { static: true }) paginator!: TemplateRef<any> | null;
|
||||||
|
@ContentChild('captionBox', { static: true }) captionBox!: TemplateRef<any> | null;
|
||||||
|
@ContentChild('emptyMessageCard', { static: true }) emptyMessageCard!: TemplateRef<any> | null;
|
||||||
|
|
||||||
|
@Output() onEdit = new EventEmitter<any>();
|
||||||
|
@Output() onDelete = new EventEmitter<any>();
|
||||||
|
@Output() onDetails = new EventEmitter<any>();
|
||||||
|
@Output() onAdd = new EventEmitter<void>();
|
||||||
|
@Output() onChangePage = new EventEmitter<any>();
|
||||||
|
@Output() onThumbnailClick = new EventEmitter<any>();
|
||||||
|
@Output() onRefresh = new EventEmitter();
|
||||||
|
|
||||||
|
filterDrawerVisible = signal<boolean>(false);
|
||||||
|
expandedRows: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
|
edit = (item: I) => {
|
||||||
|
this.onEdit.emit(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
remove = (item: I) => {
|
||||||
|
this.onDelete.emit(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
details = (item: I) => {
|
||||||
|
this.onDetails.emit(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
openAddForm = () => {
|
||||||
|
this.onAdd.emit();
|
||||||
|
};
|
||||||
|
|
||||||
|
openFilter = () => {
|
||||||
|
this.filterDrawerVisible.set(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
closeFilter = () => {
|
||||||
|
this.filterDrawerVisible.set(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
onPage = ($event: any) => {
|
||||||
|
this.onChangePage.emit($event);
|
||||||
|
};
|
||||||
|
|
||||||
|
openThumbnailModal = (item: I) => {
|
||||||
|
// this.
|
||||||
|
};
|
||||||
|
|
||||||
|
getPreviewClasses(item: Record<string, any>, column: IColumn): any {
|
||||||
|
if (!item || !column || column.customDataModel) return '';
|
||||||
|
try {
|
||||||
|
const { field } = column;
|
||||||
|
|
||||||
|
const data = item[String(field)];
|
||||||
|
switch (column.type) {
|
||||||
|
case 'date':
|
||||||
|
if (column.dateOption?.expiredMode) {
|
||||||
|
if (jalaliDiff(new Date(), data) > 0) {
|
||||||
|
return 'text-error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCell(item: Record<string, any>, column: IColumn): any {
|
||||||
|
if (!item || !column) return '';
|
||||||
|
try {
|
||||||
|
let { field, type } = column;
|
||||||
|
|
||||||
|
if (column.customDataModel) {
|
||||||
|
return this.renderCustom(column, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = item[String(field)];
|
||||||
|
if (column.type === 'nested') {
|
||||||
|
const path = column.nestedOption?.path;
|
||||||
|
if (!path) return '-';
|
||||||
|
data = path
|
||||||
|
.split('.')
|
||||||
|
.reduce((obj, key) => (obj && obj[key] !== undefined ? obj[key] : null), item);
|
||||||
|
type = column.nestedOption?.type || 'simple';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'id':
|
||||||
|
if (!data) return '-';
|
||||||
|
return `${data.slice(0, 5)}...${data.slice(data.length - 5, data.length)}`;
|
||||||
|
case 'date':
|
||||||
|
if (!data) return '-';
|
||||||
|
return formatJalali(data);
|
||||||
|
case 'boolean':
|
||||||
|
return data ? 'بله' : 'خیر';
|
||||||
|
case 'price':
|
||||||
|
return formatWithCurrency(data, false, 'ریال');
|
||||||
|
case 'number':
|
||||||
|
return data || 0;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data === undefined || data === null) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
return data.title || '-';
|
||||||
|
}
|
||||||
|
return data || '-';
|
||||||
|
} catch (e) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTemplate(col: IColumn): TemplateRef<any> | null {
|
||||||
|
const v = col.customDataModel;
|
||||||
|
return v instanceof TemplateRef ? (v as TemplateRef<any>) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCustom(column: IColumn, item: any): any {
|
||||||
|
const v = column.customDataModel;
|
||||||
|
|
||||||
|
if (!v) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof v === 'function') {
|
||||||
|
try {
|
||||||
|
return (v as (item: any) => any)(item);
|
||||||
|
} catch {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof v === 'string') return v;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsCount = computed(() => {
|
||||||
|
let totalCount = 0;
|
||||||
|
if (this.showEdit) totalCount += 1;
|
||||||
|
if (this.showDelete) totalCount += 1;
|
||||||
|
if (this.showDetails) totalCount += 1;
|
||||||
|
return totalCount;
|
||||||
|
});
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.onRefresh.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<div class="h-full flex flex-col gap-4">
|
||||||
|
<p-table
|
||||||
|
[columns]="columns"
|
||||||
|
[scrollable]="true"
|
||||||
|
[value]="items"
|
||||||
|
[loading]="loading"
|
||||||
|
columnResizeMode="fit"
|
||||||
|
stripedRows
|
||||||
|
[showFirstLastIcon]="false"
|
||||||
|
[expandedRowKeys]="expandedRows"
|
||||||
|
[dataKey]="dataKey"
|
||||||
|
class="max-sm:hidden! grow flex! flex-col overflow-hidden"
|
||||||
|
[tableStyleClass]="!items.length && !loading ? 'h-full' : ''"
|
||||||
|
>
|
||||||
|
@if (captionBox) {
|
||||||
|
<ng-template pTemplate="caption">
|
||||||
|
<ng-container [ngTemplateOutlet]="captionBox"> </ng-container>
|
||||||
|
</ng-template>
|
||||||
|
}
|
||||||
|
|
||||||
|
<ng-template #header let-columns>
|
||||||
|
<tr>
|
||||||
|
@for (col of columns; track col.header) {
|
||||||
|
<th
|
||||||
|
[style]="
|
||||||
|
col.type === 'index' ? { width: '3rem', minWidth: '3rem' } : { width: col.width, minWidth: col.minWidth }
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ col.header }}
|
||||||
|
</th>
|
||||||
|
}
|
||||||
|
@if (actionsCount()) {
|
||||||
|
<th type="th" [style]="{ width: `${actionsCount() * 2 + (actionsCount() - 1) * 0.25}rem` }"></th>
|
||||||
|
}
|
||||||
|
@if (expandable) {
|
||||||
|
<th style="width: 3rem"></th>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #body let-item let-i="rowIndex" let-expanded="expanded">
|
||||||
|
<tr>
|
||||||
|
@for (col of columns; track col.field) {
|
||||||
|
<td>
|
||||||
|
@if (col.type === "index") {
|
||||||
|
{{ i * (currentPage || 1) + 1 }}
|
||||||
|
} @else if (col.type === "thumbnail") {
|
||||||
|
<div
|
||||||
|
class="w-20 h-20 rounded-2xl overflow-hidden bg-surface-50 cursor-pointer"
|
||||||
|
(click)="openThumbnailModal(item)"
|
||||||
|
>
|
||||||
|
@if (item[col.field]) {
|
||||||
|
<img [src]="item[col.field]" class="w-full h-full object-cover" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
} @else if (getTemplate(col)) {
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="getTemplate(col)"
|
||||||
|
[ngTemplateOutletContext]="{ $implicit: item }"
|
||||||
|
></ng-container>
|
||||||
|
} @else if (col.canCopy) {
|
||||||
|
<uikit-copy [text]="getCell(item, col)"></uikit-copy>
|
||||||
|
} @else {
|
||||||
|
<span [class]="getPreviewClasses(item, col)">
|
||||||
|
{{ getCell(item, col) }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
@if (actionsCount()) {
|
||||||
|
<td
|
||||||
|
table-action-row
|
||||||
|
type="td"
|
||||||
|
[showDetails]="showDetails"
|
||||||
|
[showDelete]="showDelete"
|
||||||
|
[showEdit]="showEdit"
|
||||||
|
(edit)="edit(item)"
|
||||||
|
(delete)="remove(item)"
|
||||||
|
(details)="details(item)"
|
||||||
|
></td>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (expandable) {
|
||||||
|
<td>
|
||||||
|
<p-button
|
||||||
|
type="button"
|
||||||
|
pRipple
|
||||||
|
[pRowToggler]="item"
|
||||||
|
[text]="true"
|
||||||
|
[rounded]="true"
|
||||||
|
[plain]="true"
|
||||||
|
[icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
@if (expandable && expandColumns && expandableItemKey) {
|
||||||
|
<ng-template #expandedrow let-item>
|
||||||
|
<tr>
|
||||||
|
<td [attr.colspan]="columns.length + 1">
|
||||||
|
<div class="px-4">
|
||||||
|
@if (item[expandableItemKey]?.length) {
|
||||||
|
<p-table
|
||||||
|
[columns]="expandColumns || []"
|
||||||
|
[scrollable]="true"
|
||||||
|
[value]="item[expandableItemKey]"
|
||||||
|
[loading]="loading"
|
||||||
|
columnResizeMode="fit"
|
||||||
|
stripedRows
|
||||||
|
[showFirstLastIcon]="false"
|
||||||
|
[expandedRowKeys]="expandedRows"
|
||||||
|
class="grow flex! flex-col overflow-hidden"
|
||||||
|
[tableStyleClass]="!item.items?.length && !loading ? 'h-full' : ''"
|
||||||
|
>
|
||||||
|
<ng-template #header let-columns>
|
||||||
|
<tr>
|
||||||
|
@for (col of expandColumns; track col.header) {
|
||||||
|
<th
|
||||||
|
[style]="
|
||||||
|
col.type === 'index'
|
||||||
|
? { width: '3rem', minWidth: '3rem' }
|
||||||
|
: { width: col.width, minWidth: col.minWidth }
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ col.header }}
|
||||||
|
</th>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #body let-item let-i="rowIndex">
|
||||||
|
<tr>
|
||||||
|
@for (col of expandColumns; track col.field) {
|
||||||
|
<td>
|
||||||
|
{{ item.name }}
|
||||||
|
@if (col.type === "index") {
|
||||||
|
{{ i * (currentPage || 1) + 1 }}
|
||||||
|
} @else if (col.type === "thumbnail") {
|
||||||
|
<div
|
||||||
|
class="w-20 h-20 rounded-2xl overflow-hidden bg-surface-50 cursor-pointer"
|
||||||
|
(click)="openThumbnailModal(item)"
|
||||||
|
>
|
||||||
|
@if (item[col.field]) {
|
||||||
|
<img [src]="item[col.field]" class="w-full h-full object-cover" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
} @else if (getTemplate(col)) {
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="getTemplate(col)"
|
||||||
|
[ngTemplateOutletContext]="{ $implicit: item }"
|
||||||
|
></ng-container>
|
||||||
|
} @else if (col.canCopy) {
|
||||||
|
<uikit-copy [text]="getCell(item, col)"></uikit-copy>
|
||||||
|
} @else {
|
||||||
|
<span [class]="getPreviewClasses(item, col)">
|
||||||
|
{{ getCell(item, col) }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #emptymessage>
|
||||||
|
<tr class="">
|
||||||
|
<td [colSpan]="(expandColumns.length + 1).toString()" class="w-full">
|
||||||
|
<uikit-empty-state
|
||||||
|
[title]="emptyPlaceholderTitle"
|
||||||
|
[description]="emptyPlaceholderDescription"
|
||||||
|
[ctaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel"
|
||||||
|
[showCTA]="showAdd"
|
||||||
|
(ctaClick)="openAddForm()"
|
||||||
|
></uikit-empty-state>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
} @else {
|
||||||
|
<div class="grid grid-cols-3 gap-4 py-2">
|
||||||
|
@for (col of expandColumns; track $index) {
|
||||||
|
<app-key-value
|
||||||
|
[label]="col.header"
|
||||||
|
[value]="item[expandableItemKey][col.field]"
|
||||||
|
[type]="col.type || 'simple'"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
}
|
||||||
|
|
||||||
|
<ng-template #emptymessage>
|
||||||
|
<tr class="">
|
||||||
|
<td [colSpan]="(columns.length + 1).toString()" class="w-full">
|
||||||
|
<ng-container [ngTemplateOutlet]="emptyMessageCard"></ng-container>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #loadingbody let-columns>
|
||||||
|
@for (i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; track i) {
|
||||||
|
<tr style="height: 46px">
|
||||||
|
@for (col of columns; track col) {
|
||||||
|
<td>
|
||||||
|
<p-skeleton></p-skeleton>
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
@if (actionsCount()) {
|
||||||
|
<td>
|
||||||
|
<p-skeleton></p-skeleton>
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
|
||||||
|
@if (showPaginator) {
|
||||||
|
<ng-container [ngTemplateOutlet]="paginator"> </ng-container>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { UikitCopyComponent, UikitEmptyStateComponent } from '@/uikit';
|
||||||
|
import { formatJalali, formatWithCurrency, jalaliDiff } from '@/utils';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
computed,
|
||||||
|
ContentChild,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
signal,
|
||||||
|
TemplateRef,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { DrawerModule } from 'primeng/drawer';
|
||||||
|
import { PaginatorModule } from 'primeng/paginator';
|
||||||
|
import { SkeletonModule } from 'primeng/skeleton';
|
||||||
|
import { TableModule } from 'primeng/table';
|
||||||
|
import { KeyValueComponent } from '../key-value.component/key-value.component';
|
||||||
|
import { TableActionRowComponent } from '../table-action-row.component';
|
||||||
|
|
||||||
|
type TDataType =
|
||||||
|
| 'simple'
|
||||||
|
| 'price'
|
||||||
|
| 'boolean'
|
||||||
|
| 'date'
|
||||||
|
| 'nested'
|
||||||
|
| 'index'
|
||||||
|
| 'id'
|
||||||
|
| 'thumbnail'
|
||||||
|
| 'number';
|
||||||
|
export interface IColumn<T = any> {
|
||||||
|
field: T extends object ? keyof T | string : string;
|
||||||
|
header: string;
|
||||||
|
width?: string;
|
||||||
|
minWidth?: string;
|
||||||
|
canCopy?: boolean;
|
||||||
|
type?: TDataType;
|
||||||
|
|
||||||
|
nestedOption?: {
|
||||||
|
path: string;
|
||||||
|
type?: TDataType;
|
||||||
|
};
|
||||||
|
thumbnailOptions?: {
|
||||||
|
editable: boolean;
|
||||||
|
deletable: boolean;
|
||||||
|
showPreview: boolean;
|
||||||
|
};
|
||||||
|
dateOption?: {
|
||||||
|
expiredMode?: boolean;
|
||||||
|
dateTime?: boolean;
|
||||||
|
onlyTime?: boolean;
|
||||||
|
};
|
||||||
|
customDataModel?: TemplateRef<any> | ((item: T) => string | number | boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-page-data-list-table-view',
|
||||||
|
templateUrl: './page-data-list-table-view.component.html',
|
||||||
|
host: {
|
||||||
|
class: 'block w-full h-full overflow-hidden',
|
||||||
|
},
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TableActionRowComponent,
|
||||||
|
UikitEmptyStateComponent,
|
||||||
|
TableModule,
|
||||||
|
ButtonModule,
|
||||||
|
SkeletonModule,
|
||||||
|
PaginatorModule,
|
||||||
|
DrawerModule,
|
||||||
|
UikitCopyComponent,
|
||||||
|
KeyValueComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppPageDataListTableView<I = any> {
|
||||||
|
@Input({ required: true }) pageTitle!: string;
|
||||||
|
@Input({ required: true }) columns!: IColumn[];
|
||||||
|
@Input({ required: true }) items!: any[];
|
||||||
|
@Input({ required: true }) loading!: boolean;
|
||||||
|
@Input({ required: true }) emptyPlaceholderTitle!: string;
|
||||||
|
@Input() addNewCtaLabel?: string;
|
||||||
|
@Input() emptyPlaceholderDescription?: string = '';
|
||||||
|
@Input() emptyPlaceholderCtaLabel?: string;
|
||||||
|
@Input() currentPage?: number = 1;
|
||||||
|
@Input() showEdit: boolean = false;
|
||||||
|
@Input() showDelete: boolean = false;
|
||||||
|
@Input() showDetails: boolean = false;
|
||||||
|
@Input() showAdd: boolean = false;
|
||||||
|
@Input() showRefresh: boolean = true;
|
||||||
|
@Input() isFiltered: boolean = false;
|
||||||
|
@Input() fullHeight?: boolean = false;
|
||||||
|
@Input() height: string = '';
|
||||||
|
@Input() expandable?: boolean = false;
|
||||||
|
@Input() expandableItemKey?: string = '';
|
||||||
|
@Input() expandColumns?: Maybe<IColumn[]> = null;
|
||||||
|
@Input() dataKey?: string = 'items';
|
||||||
|
@Input() showPaginator?: boolean;
|
||||||
|
|
||||||
|
@ContentChild('filter', { static: true }) filter!: TemplateRef<any> | null;
|
||||||
|
@ContentChild('paginator', { static: true }) paginator!: TemplateRef<any> | null;
|
||||||
|
@ContentChild('moreActions', { static: true }) moreActions!: TemplateRef<any> | null;
|
||||||
|
@ContentChild('expandableTemp', { static: false }) expandableTemp!: TemplateRef<any> | null;
|
||||||
|
@ContentChild('captionBox', { static: true }) captionBox!: TemplateRef<any> | null;
|
||||||
|
@ContentChild('emptyMessageCard', { static: true }) emptyMessageCard!: TemplateRef<any> | null;
|
||||||
|
|
||||||
|
@Output() onEdit = new EventEmitter<any>();
|
||||||
|
@Output() onDelete = new EventEmitter<any>();
|
||||||
|
@Output() onDetails = new EventEmitter<any>();
|
||||||
|
@Output() onAdd = new EventEmitter<void>();
|
||||||
|
@Output() onChangePage = new EventEmitter<any>();
|
||||||
|
@Output() onThumbnailClick = new EventEmitter<any>();
|
||||||
|
@Output() onRefresh = new EventEmitter();
|
||||||
|
|
||||||
|
filterDrawerVisible = signal<boolean>(false);
|
||||||
|
expandedRows: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
|
edit = (item: I) => {
|
||||||
|
this.onEdit.emit(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
remove = (item: I) => {
|
||||||
|
this.onDelete.emit(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
details = (item: I) => {
|
||||||
|
this.onDetails.emit(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
openAddForm = () => {
|
||||||
|
this.onAdd.emit();
|
||||||
|
};
|
||||||
|
|
||||||
|
openFilter = () => {
|
||||||
|
this.filterDrawerVisible.set(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
closeFilter = () => {
|
||||||
|
this.filterDrawerVisible.set(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
onPage = ($event: any) => {
|
||||||
|
this.onChangePage.emit($event);
|
||||||
|
};
|
||||||
|
|
||||||
|
openThumbnailModal = (item: I) => {
|
||||||
|
// this.
|
||||||
|
};
|
||||||
|
|
||||||
|
getPreviewClasses(item: Record<string, any>, column: IColumn): any {
|
||||||
|
if (!item || !column || column.customDataModel) return '';
|
||||||
|
try {
|
||||||
|
const { field } = column;
|
||||||
|
|
||||||
|
const data = item[String(field)];
|
||||||
|
switch (column.type) {
|
||||||
|
case 'date':
|
||||||
|
if (column.dateOption?.expiredMode) {
|
||||||
|
if (jalaliDiff(new Date(), data) > 0) {
|
||||||
|
return 'text-error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCell(item: Record<string, any>, column: IColumn): any {
|
||||||
|
if (!item || !column) return '';
|
||||||
|
try {
|
||||||
|
let { field, type } = column;
|
||||||
|
|
||||||
|
if (column.customDataModel) {
|
||||||
|
return this.renderCustom(column, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = item[String(field)];
|
||||||
|
if (column.type === 'nested') {
|
||||||
|
const path = column.nestedOption?.path;
|
||||||
|
if (!path) return '-';
|
||||||
|
data = path
|
||||||
|
.split('.')
|
||||||
|
.reduce((obj, key) => (obj && obj[key] !== undefined ? obj[key] : null), item);
|
||||||
|
type = column.nestedOption?.type || 'simple';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'id':
|
||||||
|
if (!data) return '-';
|
||||||
|
return `${data.slice(0, 5)}...${data.slice(data.length - 5, data.length)}`;
|
||||||
|
case 'date':
|
||||||
|
if (!data) return '-';
|
||||||
|
return formatJalali(data);
|
||||||
|
case 'boolean':
|
||||||
|
return data ? 'بله' : 'خیر';
|
||||||
|
case 'price':
|
||||||
|
return formatWithCurrency(data, false, 'ریال');
|
||||||
|
case 'number':
|
||||||
|
return data || 0;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data === undefined || data === null) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
return data.title || '-';
|
||||||
|
}
|
||||||
|
return data || '-';
|
||||||
|
} catch (e) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTemplate(col: IColumn): TemplateRef<any> | null {
|
||||||
|
const v = col.customDataModel;
|
||||||
|
return v instanceof TemplateRef ? (v as TemplateRef<any>) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCustom(column: IColumn, item: any): any {
|
||||||
|
const v = column.customDataModel;
|
||||||
|
|
||||||
|
if (!v) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof v === 'function') {
|
||||||
|
try {
|
||||||
|
return (v as (item: any) => any)(item);
|
||||||
|
} catch {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof v === 'string') return v;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsCount = computed(() => {
|
||||||
|
let totalCount = 0;
|
||||||
|
if (this.showEdit) totalCount += 1;
|
||||||
|
if (this.showDelete) totalCount += 1;
|
||||||
|
if (this.showDetails) totalCount += 1;
|
||||||
|
return totalCount;
|
||||||
|
});
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.onRefresh.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,24 @@
|
|||||||
<div class="h-full bg-surface-overlay rounded-(--p-card-border-radius) overflow-hidden">
|
<div class="h-full bg-surface-overlay rounded-(--p-card-border-radius) overflow-hidden">
|
||||||
<div class="h-full flex flex-col gap-4">
|
@if (!isMobile) {
|
||||||
<p-table
|
<app-page-data-list-table-view
|
||||||
|
[pageTitle]="pageTitle"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
[scrollable]="true"
|
[items]="items"
|
||||||
[value]="items"
|
|
||||||
[loading]="loading"
|
[loading]="loading"
|
||||||
columnResizeMode="fit"
|
[emptyPlaceholderTitle]="emptyPlaceholderTitle"
|
||||||
stripedRows
|
[emptyPlaceholderDescription]="emptyPlaceholderDescription"
|
||||||
[showFirstLastIcon]="false"
|
[emptyPlaceholderCtaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel"
|
||||||
[expandedRowKeys]="expandedRows"
|
[addNewCtaLabel]="addNewCtaLabel"
|
||||||
[dataKey]="dataKey"
|
[currentPage]="currentPage"
|
||||||
class="grow flex! flex-col overflow-hidden"
|
[showEdit]="showEdit"
|
||||||
[tableStyleClass]="!items.length && !loading ? 'h-full' : ''"
|
[showDelete]="showDelete"
|
||||||
|
[showDetails]="showDetails"
|
||||||
|
(onEdit)="edit($event)"
|
||||||
|
(onDelete)="remove($event)"
|
||||||
|
(onDetails)="details($event)"
|
||||||
>
|
>
|
||||||
@if (pageTitle || showAdd || filter || caption || showRefresh || moreActions) {
|
@if (pageTitle || showAdd || filter || caption || showRefresh || moreActions) {
|
||||||
<ng-template pTemplate="caption">
|
<ng-template #captionBox>
|
||||||
<ng-container [ngTemplateOutlet]="caption">
|
<ng-container [ngTemplateOutlet]="caption">
|
||||||
<div class="flex justify-between items-center gap-4">
|
<div class="flex justify-between items-center gap-4">
|
||||||
<h5 class="font-bold">{{ pageTitle }}</h5>
|
<h5 class="font-bold">{{ pageTitle }}</h5>
|
||||||
@@ -27,15 +31,21 @@
|
|||||||
icon="pi pi-filter"
|
icon="pi pi-filter"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
badgeSeverity="info"
|
badgeSeverity="info"
|
||||||
|
size="small"
|
||||||
[badge]="isFiltered ? '1' : undefined"
|
[badge]="isFiltered ? '1' : undefined"
|
||||||
(click)="openFilter()"
|
(click)="openFilter()"
|
||||||
></p-button>
|
></p-button>
|
||||||
}
|
}
|
||||||
@if (showRefresh) {
|
@if (showRefresh) {
|
||||||
<p-button icon="pi pi-refresh" outlined (click)="refresh()"></p-button>
|
<p-button icon="pi pi-refresh" outlined size="small" (click)="refresh()"></p-button>
|
||||||
}
|
}
|
||||||
@if (showAdd) {
|
@if (showAdd) {
|
||||||
<p-button label="{{ addNewCtaLabel }}" icon="pi pi-plus" (click)="openAddForm()"></p-button>
|
<p-button
|
||||||
|
label="{{ addNewCtaLabel }}"
|
||||||
|
icon="pi pi-plus"
|
||||||
|
size="small"
|
||||||
|
(click)="openAddForm()"
|
||||||
|
></p-button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -43,226 +53,100 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
}
|
||||||
|
<ng-template #emptyMessageCard>
|
||||||
<ng-template #header let-columns>
|
<uikit-empty-state
|
||||||
<tr>
|
[title]="emptyPlaceholderTitle"
|
||||||
@for (col of columns; track col.header) {
|
[description]="emptyPlaceholderDescription"
|
||||||
<th
|
[ctaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel"
|
||||||
[style]="
|
[showCTA]="showAdd"
|
||||||
col.type === 'index'
|
(ctaClick)="openAddForm()"
|
||||||
? { width: '3rem', minWidth: '3rem' }
|
></uikit-empty-state>
|
||||||
: { width: col.width, minWidth: col.minWidth }
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ col.header }}
|
|
||||||
</th>
|
|
||||||
}
|
|
||||||
@if (actionsCount()) {
|
|
||||||
<th type="th" [style]="{ width: `${actionsCount() * 2 + (actionsCount() - 1) * 0.25}rem` }"></th>
|
|
||||||
}
|
|
||||||
@if (expandable) {
|
|
||||||
<th style="width: 3rem"></th>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
<ng-template #paginator>
|
||||||
<ng-template #body let-item let-i="rowIndex" let-expanded="expanded">
|
<app-paginator
|
||||||
<tr>
|
[currentPage]="currentPage || 1"
|
||||||
@for (col of columns; track col.field) {
|
[totalRecords]="totalRecords"
|
||||||
<td>
|
[perPage]="perPage || 10"
|
||||||
@if (col.type === "index") {
|
[loading]="loading"
|
||||||
{{ i * (currentPage || 1) + 1 }}
|
(onChange)="onPage($event)"
|
||||||
} @else if (col.type === "thumbnail") {
|
/>
|
||||||
<div
|
</ng-template>
|
||||||
class="w-20 h-20 rounded-2xl overflow-hidden bg-surface-50 cursor-pointer"
|
</app-page-data-list-table-view>
|
||||||
(click)="openThumbnailModal(item)"
|
} @else {
|
||||||
>
|
<app-page-data-list-grid-view
|
||||||
@if (item[col.field]) {
|
[pageTitle]="pageTitle"
|
||||||
<img [src]="item[col.field]" class="w-full h-full object-cover" />
|
[columns]="columns"
|
||||||
|
[items]="items"
|
||||||
|
[loading]="loading"
|
||||||
|
[emptyPlaceholderTitle]="emptyPlaceholderTitle"
|
||||||
|
[emptyPlaceholderDescription]="emptyPlaceholderDescription"
|
||||||
|
[emptyPlaceholderCtaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel"
|
||||||
|
[addNewCtaLabel]="addNewCtaLabel"
|
||||||
|
[currentPage]="currentPage"
|
||||||
|
[showEdit]="showEdit"
|
||||||
|
[showDelete]="showDelete"
|
||||||
|
[showDetails]="showDetails"
|
||||||
|
(onEdit)="edit($event)"
|
||||||
|
(onDelete)="remove($event)"
|
||||||
|
(onDetails)="details($event)"
|
||||||
|
>
|
||||||
|
@if (pageTitle || showAdd || filter || caption || showRefresh || moreActions) {
|
||||||
|
<ng-template #captionBox>
|
||||||
|
<ng-container [ngTemplateOutlet]="caption">
|
||||||
|
<div class="flex justify-between items-center gap-4">
|
||||||
|
<h5 class="font-bold">{{ pageTitle }}</h5>
|
||||||
|
@if (showAdd || filter || showRefresh || moreActions) {
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ng-container [ngTemplateOutlet]="moreActions" />
|
||||||
|
@if (filter) {
|
||||||
|
<p-button
|
||||||
|
label="فیلتر"
|
||||||
|
icon="pi pi-filter"
|
||||||
|
variant="outlined"
|
||||||
|
badgeSeverity="info"
|
||||||
|
size="small"
|
||||||
|
[badge]="isFiltered ? '1' : undefined"
|
||||||
|
(click)="openFilter()"
|
||||||
|
></p-button>
|
||||||
|
}
|
||||||
|
@if (showRefresh) {
|
||||||
|
<p-button icon="pi pi-refresh" outlined size="small" (click)="refresh()"></p-button>
|
||||||
|
}
|
||||||
|
@if (showAdd) {
|
||||||
|
<p-button
|
||||||
|
label="{{ addNewCtaLabel }}"
|
||||||
|
icon="pi pi-plus"
|
||||||
|
size="small"
|
||||||
|
(click)="openAddForm()"
|
||||||
|
></p-button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} @else if (getTemplate(col)) {
|
|
||||||
<ng-container
|
|
||||||
[ngTemplateOutlet]="getTemplate(col)"
|
|
||||||
[ngTemplateOutletContext]="{ $implicit: item }"
|
|
||||||
></ng-container>
|
|
||||||
} @else if (col.canCopy) {
|
|
||||||
<uikit-copy [text]="getCell(item, col)"></uikit-copy>
|
|
||||||
} @else {
|
|
||||||
<span [class]="getPreviewClasses(item, col)">
|
|
||||||
{{ getCell(item, col) }}
|
|
||||||
</span>
|
|
||||||
}
|
}
|
||||||
</td>
|
</div>
|
||||||
}
|
</ng-container>
|
||||||
@if (actionsCount()) {
|
|
||||||
<td
|
|
||||||
table-action-row
|
|
||||||
type="td"
|
|
||||||
[showDetails]="showDetails"
|
|
||||||
[showDelete]="showDelete"
|
|
||||||
[showEdit]="showEdit"
|
|
||||||
(edit)="edit(item)"
|
|
||||||
(delete)="remove(item)"
|
|
||||||
(details)="details(item)"
|
|
||||||
></td>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (expandable) {
|
|
||||||
<td>
|
|
||||||
<p-button
|
|
||||||
type="button"
|
|
||||||
pRipple
|
|
||||||
[pRowToggler]="item"
|
|
||||||
[text]="true"
|
|
||||||
[rounded]="true"
|
|
||||||
[plain]="true"
|
|
||||||
[icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
@if (expandable && expandColumns && expandableItemKey) {
|
|
||||||
<ng-template #expandedrow let-item>
|
|
||||||
<tr>
|
|
||||||
<td [attr.colspan]="columns.length + 1">
|
|
||||||
<div class="px-4">
|
|
||||||
@if (item[expandableItemKey]?.length) {
|
|
||||||
<p-table
|
|
||||||
[columns]="expandColumns || []"
|
|
||||||
[scrollable]="true"
|
|
||||||
[value]="item[expandableItemKey]"
|
|
||||||
[loading]="loading"
|
|
||||||
columnResizeMode="fit"
|
|
||||||
stripedRows
|
|
||||||
[showFirstLastIcon]="false"
|
|
||||||
[expandedRowKeys]="expandedRows"
|
|
||||||
class="grow flex! flex-col overflow-hidden"
|
|
||||||
[tableStyleClass]="!item.items?.length && !loading ? 'h-full' : ''"
|
|
||||||
>
|
|
||||||
<ng-template #header let-columns>
|
|
||||||
<tr>
|
|
||||||
@for (col of expandColumns; track col.header) {
|
|
||||||
<th
|
|
||||||
[style]="
|
|
||||||
col.type === 'index'
|
|
||||||
? { width: '3rem', minWidth: '3rem' }
|
|
||||||
: { width: col.width, minWidth: col.minWidth }
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ col.header }}
|
|
||||||
</th>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #body let-item let-i="rowIndex">
|
|
||||||
<tr>
|
|
||||||
@for (col of expandColumns; track col.field) {
|
|
||||||
<td>
|
|
||||||
{{ item.name }}
|
|
||||||
@if (col.type === "index") {
|
|
||||||
{{ i * (currentPage || 1) + 1 }}
|
|
||||||
} @else if (col.type === "thumbnail") {
|
|
||||||
<div
|
|
||||||
class="w-20 h-20 rounded-2xl overflow-hidden bg-surface-50 cursor-pointer"
|
|
||||||
(click)="openThumbnailModal(item)"
|
|
||||||
>
|
|
||||||
@if (item[col.field]) {
|
|
||||||
<img [src]="item[col.field]" class="w-full h-full object-cover" />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
} @else if (getTemplate(col)) {
|
|
||||||
<ng-container
|
|
||||||
[ngTemplateOutlet]="getTemplate(col)"
|
|
||||||
[ngTemplateOutletContext]="{ $implicit: item }"
|
|
||||||
></ng-container>
|
|
||||||
} @else if (col.canCopy) {
|
|
||||||
<uikit-copy [text]="getCell(item, col)"></uikit-copy>
|
|
||||||
} @else {
|
|
||||||
<span [class]="getPreviewClasses(item, col)">
|
|
||||||
{{ getCell(item, col) }}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #emptymessage>
|
|
||||||
<tr class="">
|
|
||||||
<td [colSpan]="(expandColumns.length + 1).toString()" class="w-full">
|
|
||||||
<uikit-empty-state
|
|
||||||
[title]="emptyPlaceholderTitle"
|
|
||||||
[description]="emptyPlaceholderDescription"
|
|
||||||
[ctaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel"
|
|
||||||
[showCTA]="showAdd"
|
|
||||||
(ctaClick)="openAddForm()"
|
|
||||||
></uikit-empty-state>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
} @else {
|
|
||||||
<div class="grid grid-cols-3 gap-4 py-2">
|
|
||||||
@for (col of expandColumns; track $index) {
|
|
||||||
<app-key-value
|
|
||||||
[label]="col.header"
|
|
||||||
[value]="item[expandableItemKey][col.field]"
|
|
||||||
[type]="col.type || 'simple'"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
}
|
||||||
|
<ng-template #emptyMessageCard>
|
||||||
<ng-template #emptymessage>
|
<uikit-empty-state
|
||||||
<tr class="">
|
[title]="emptyPlaceholderTitle"
|
||||||
<td [colSpan]="(columns.length + 1).toString()" class="w-full">
|
[description]="emptyPlaceholderDescription"
|
||||||
<uikit-empty-state
|
[ctaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel"
|
||||||
[title]="emptyPlaceholderTitle"
|
[showCTA]="showAdd"
|
||||||
[description]="emptyPlaceholderDescription"
|
(ctaClick)="openAddForm()"
|
||||||
[ctaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel"
|
></uikit-empty-state>
|
||||||
[showCTA]="showAdd"
|
|
||||||
(ctaClick)="openAddForm()"
|
|
||||||
></uikit-empty-state>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
<ng-template #paginator>
|
||||||
<ng-template #loadingbody let-columns>
|
<app-paginator
|
||||||
@for (i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; track i) {
|
[currentPage]="currentPage || 1"
|
||||||
<tr style="height: 46px">
|
[totalRecords]="totalRecords"
|
||||||
@for (col of columns; track col) {
|
[perPage]="perPage || 10"
|
||||||
<td>
|
[loading]="loading"
|
||||||
<p-skeleton></p-skeleton>
|
(onChange)="onPage($event)"
|
||||||
</td>
|
/>
|
||||||
}
|
|
||||||
@if (actionsCount()) {
|
|
||||||
<td>
|
|
||||||
<p-skeleton></p-skeleton>
|
|
||||||
</td>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</p-table>
|
</app-page-data-list-grid-view>
|
||||||
@if (showPaginator) {
|
}
|
||||||
<app-paginator
|
|
||||||
[currentPage]="currentPage || 1"
|
|
||||||
[totalRecords]="totalRecords"
|
|
||||||
[perPage]="perPage || 10"
|
|
||||||
[loading]="loading"
|
|
||||||
(onChange)="onPage($event)"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@if (filter) {
|
@if (filter) {
|
||||||
<p-drawer
|
<p-drawer
|
||||||
[visible]="filterDrawerVisible()"
|
[visible]="filterDrawerVisible()"
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Maybe } from '@/core';
|
import { Maybe } from '@/core';
|
||||||
import { PaginatorComponent, UikitCopyComponent, UikitEmptyStateComponent } from '@/uikit';
|
import { PaginatorComponent, UikitEmptyStateComponent } from '@/uikit';
|
||||||
import { formatJalali, formatWithCurrency, jalaliDiff } from '@/utils';
|
import { jalaliDiff } from '@/utils';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
computed,
|
|
||||||
ContentChild,
|
ContentChild,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
|
HostListener,
|
||||||
Input,
|
Input,
|
||||||
Output,
|
Output,
|
||||||
Renderer2,
|
Renderer2,
|
||||||
@@ -19,10 +19,19 @@ import { DrawerModule } from 'primeng/drawer';
|
|||||||
import { PaginatorModule } from 'primeng/paginator';
|
import { PaginatorModule } from 'primeng/paginator';
|
||||||
import { SkeletonModule } from 'primeng/skeleton';
|
import { SkeletonModule } from 'primeng/skeleton';
|
||||||
import { TableModule } from 'primeng/table';
|
import { TableModule } from 'primeng/table';
|
||||||
import { KeyValueComponent } from '../key-value.component/key-value.component';
|
import { AppPageDataListGridView } from './page-data-list-grid-view.component';
|
||||||
import { TableActionRowComponent } from '../table-action-row.component';
|
import { AppPageDataListTableView } from './page-data-list-table-view.component';
|
||||||
|
|
||||||
type TDataType = 'simple' | 'price' | 'boolean' | 'date' | 'nested' | 'index' | 'id' | 'thumbnail';
|
type TDataType =
|
||||||
|
| 'simple'
|
||||||
|
| 'price'
|
||||||
|
| 'boolean'
|
||||||
|
| 'date'
|
||||||
|
| 'nested'
|
||||||
|
| 'index'
|
||||||
|
| 'id'
|
||||||
|
| 'thumbnail'
|
||||||
|
| 'number';
|
||||||
export interface IColumn<T = any> {
|
export interface IColumn<T = any> {
|
||||||
field: T extends object ? keyof T | string : string;
|
field: T extends object ? keyof T | string : string;
|
||||||
header: string;
|
header: string;
|
||||||
@@ -56,19 +65,18 @@ export interface IColumn<T = any> {
|
|||||||
},
|
},
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
TableActionRowComponent,
|
|
||||||
UikitEmptyStateComponent,
|
|
||||||
TableModule,
|
TableModule,
|
||||||
ButtonModule,
|
ButtonModule,
|
||||||
SkeletonModule,
|
SkeletonModule,
|
||||||
PaginatorModule,
|
PaginatorModule,
|
||||||
DrawerModule,
|
DrawerModule,
|
||||||
PaginatorComponent,
|
PaginatorComponent,
|
||||||
UikitCopyComponent,
|
AppPageDataListTableView,
|
||||||
KeyValueComponent,
|
AppPageDataListGridView,
|
||||||
|
UikitEmptyStateComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PageDataListComponent<I> {
|
export class PageDataListComponent<I = any> {
|
||||||
constructor(
|
constructor(
|
||||||
private host: ElementRef,
|
private host: ElementRef,
|
||||||
private renderer: Renderer2,
|
private renderer: Renderer2,
|
||||||
@@ -77,7 +85,7 @@ export class PageDataListComponent<I> {
|
|||||||
@Input({ required: true }) pageTitle!: string;
|
@Input({ required: true }) pageTitle!: string;
|
||||||
@Input() addNewCtaLabel?: string;
|
@Input() addNewCtaLabel?: string;
|
||||||
@Input({ required: true }) columns!: IColumn[];
|
@Input({ required: true }) columns!: IColumn[];
|
||||||
@Input({ required: true }) items!: I[];
|
@Input({ required: true }) items!: any[];
|
||||||
@Input({ required: true }) loading!: boolean;
|
@Input({ required: true }) loading!: boolean;
|
||||||
@Input() emptyPlaceholderTitle: string = 'موردی برای نمایش وجود ندارد';
|
@Input() emptyPlaceholderTitle: string = 'موردی برای نمایش وجود ندارد';
|
||||||
@Input() emptyPlaceholderDescription?: string = '';
|
@Input() emptyPlaceholderDescription?: string = '';
|
||||||
@@ -123,17 +131,19 @@ export class PageDataListComponent<I> {
|
|||||||
);
|
);
|
||||||
// if (this.fullHeight) {
|
// if (this.fullHeight) {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
this.updateViewportMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
edit = (item: I) => {
|
edit = (item: any) => {
|
||||||
this.onEdit.emit(item);
|
this.onEdit.emit(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
remove = (item: I) => {
|
remove = (item: any) => {
|
||||||
this.onDelete.emit(item);
|
this.onDelete.emit(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
details = (item: I) => {
|
details = (item: any) => {
|
||||||
this.onDetails.emit(item);
|
this.onDetails.emit(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,84 +193,18 @@ export class PageDataListComponent<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getCell(item: Record<string, any>, column: IColumn): any {
|
|
||||||
if (!item || !column) return '';
|
|
||||||
try {
|
|
||||||
let { field, type } = column;
|
|
||||||
|
|
||||||
if (column.customDataModel) {
|
|
||||||
return this.renderCustom(column, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = item[String(field)];
|
|
||||||
if (column.type === 'nested') {
|
|
||||||
const path = column.nestedOption?.path;
|
|
||||||
if (!path) return '-';
|
|
||||||
data = path
|
|
||||||
.split('.')
|
|
||||||
.reduce((obj, key) => (obj && obj[key] !== undefined ? obj[key] : null), item);
|
|
||||||
type = column.nestedOption?.type || 'simple';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type) {
|
|
||||||
switch (type) {
|
|
||||||
case 'id':
|
|
||||||
if (!data) return '-';
|
|
||||||
return `${data.slice(0, 5)}...${data.slice(data.length - 5, data.length)}`;
|
|
||||||
case 'date':
|
|
||||||
if (!data) return '-';
|
|
||||||
return formatJalali(data);
|
|
||||||
case 'boolean':
|
|
||||||
return data ? 'بله' : 'خیر';
|
|
||||||
case 'price':
|
|
||||||
return formatWithCurrency(data, false, 'ریال');
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data === undefined || data === null) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
if (typeof data === 'object') {
|
|
||||||
return data.title || '-';
|
|
||||||
}
|
|
||||||
return data || '-';
|
|
||||||
} catch (e) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getTemplate(col: IColumn): TemplateRef<any> | null {
|
|
||||||
const v = col.customDataModel;
|
|
||||||
return v instanceof TemplateRef ? (v as TemplateRef<any>) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCustom(column: IColumn, item: any): any {
|
|
||||||
const v = column.customDataModel;
|
|
||||||
|
|
||||||
if (!v) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (typeof v === 'function') {
|
|
||||||
try {
|
|
||||||
return (v as (item: any) => any)(item);
|
|
||||||
} catch {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof v === 'string') return v;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
actionsCount = computed(() => {
|
|
||||||
let totalCount = 0;
|
|
||||||
if (this.showEdit) totalCount += 1;
|
|
||||||
if (this.showDelete) totalCount += 1;
|
|
||||||
if (this.showDetails) totalCount += 1;
|
|
||||||
return totalCount;
|
|
||||||
});
|
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.onRefresh.emit();
|
this.onRefresh.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMobile = false;
|
||||||
|
|
||||||
|
@HostListener('window:resize')
|
||||||
|
onResize() {
|
||||||
|
this.updateViewportMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateViewportMode() {
|
||||||
|
this.isMobile = typeof window !== 'undefined' && window.innerWidth <= 768;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
:root {
|
:root {
|
||||||
--p-drawer-header-padding: 0.5rem 0.875rem !important;
|
--p-drawer-header-padding: 0.5rem 0.875rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.listKeyValue {
|
||||||
|
@apply grid md:grid-cols-3 sm:grid-cols-2 sm:gap-4 gap-3 items-center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// Development environment configuration
|
// Development environment configuration
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
apiBaseUrl: 'http://194.59.214.243:5002',
|
// apiBaseUrl: 'http://194.59.214.243:5002',
|
||||||
|
apiBaseUrl: 'http://192.168.128.73:5002',
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 5000,
|
port: 5001,
|
||||||
enableLogging: false,
|
enableLogging: false,
|
||||||
enableDebug: false,
|
enableDebug: false,
|
||||||
enableNativeBridge: false,
|
enableNativeBridge: false,
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
// Staging environment configuration
|
// Development environment configuration
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
apiBaseUrl: 'https://staging-api.yourdomain.com',
|
// apiBaseUrl: 'http://194.59.214.243:5002',
|
||||||
host: 'staging.yourdomain.com',
|
apiBaseUrl: 'http://192.168.128.73:5002',
|
||||||
port: 443,
|
// apiBaseUrl: 'http://localhost:5002',
|
||||||
enableNativeBridge: false,
|
// host: 'http://194.59.214.243',
|
||||||
apiEndpoints: {
|
host: '0.0.0.0',
|
||||||
auth: '/api/auth',
|
port: 5005,
|
||||||
users: '/api/users',
|
|
||||||
// Add more endpoints as needed
|
|
||||||
},
|
|
||||||
enableLogging: true,
|
enableLogging: true,
|
||||||
enableDebug: true,
|
enableDebug: true,
|
||||||
|
enableNativeBridge: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// TIS tenant environment configuration
|
// TIS tenant environment configuration
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
apiBaseUrl: 'http://194.59.214.243:5002',
|
// apiBaseUrl: 'http://194.59.214.243:5002',
|
||||||
|
apiBaseUrl: 'http://192.168.128.73:5002',
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 5000,
|
port: 5000,
|
||||||
enableLogging: false,
|
enableLogging: false,
|
||||||
|
|||||||
+5
-4
@@ -32,6 +32,7 @@ const MyPreset = definePreset(Aura, {
|
|||||||
},
|
},
|
||||||
semantic: {
|
semantic: {
|
||||||
primary: {
|
primary: {
|
||||||
|
0: '{surface.0}',
|
||||||
50: '{surface.50}',
|
50: '{surface.50}',
|
||||||
100: '{surface.100}',
|
100: '{surface.100}',
|
||||||
200: '{surface.200}',
|
200: '{surface.200}',
|
||||||
@@ -49,15 +50,15 @@ const MyPreset = definePreset(Aura, {
|
|||||||
light: {
|
light: {
|
||||||
primary: {
|
primary: {
|
||||||
color: '{primary.950}',
|
color: '{primary.950}',
|
||||||
contrastColor: '#ffffff',
|
contrastColor: '#f0f0f0',
|
||||||
hoverColor: '{primary.800}',
|
hoverColor: '{primary.800}',
|
||||||
activeColor: '{primary.700}',
|
activeColor: '{primary.700}',
|
||||||
},
|
},
|
||||||
highlight: {
|
highlight: {
|
||||||
background: '{primary.950}',
|
background: '{primary.950}',
|
||||||
focusBackground: '{primary.700}',
|
focusBackground: '{primary.700}',
|
||||||
color: '#ffffff',
|
color: '#f0f0f0',
|
||||||
focusColor: '#ffffff',
|
focusColor: '#f0f0f0',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
@@ -78,7 +79,7 @@ const MyPreset = definePreset(Aura, {
|
|||||||
},
|
},
|
||||||
primitive: {
|
primitive: {
|
||||||
...updateSurfacePalette({
|
...updateSurfacePalette({
|
||||||
0: '#ffffff',
|
0: '#f0f0f0',
|
||||||
50: '#f8fafc',
|
50: '#f8fafc',
|
||||||
100: '#f1f5f9',
|
100: '#f1f5f9',
|
||||||
200: '#e2e8f0',
|
200: '#e2e8f0',
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { POS_ROUTES } from '@/domains/pos/routes';
|
import { POS_ROUTES } from '@/domains/pos/routes';
|
||||||
|
import { AppLayout } from '@/layout/default/app.layout.component';
|
||||||
import { AuthComponent } from '@/modules/auth/pages/auth.component';
|
import { AuthComponent } from '@/modules/auth/pages/auth.component';
|
||||||
import { Notfound } from '@/pages/notfound/notfound.component';
|
import { Notfound } from '@/pages/notfound/notfound.component';
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
export const appRoutes: Routes = [
|
export const appRoutes: Routes = [
|
||||||
{ path: '', redirectTo: 'pos', pathMatch: 'full' },
|
{
|
||||||
POS_ROUTES,
|
path: '',
|
||||||
|
component: AppLayout,
|
||||||
|
children: [POS_ROUTES],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'auth',
|
path: 'auth',
|
||||||
component: AuthComponent,
|
component: AuthComponent,
|
||||||
|
|||||||
Reference in New Issue
Block a user