feat: implement stock keeping unit management form and list components
- Added StockKeepingUnitFormComponent for creating and editing stock keeping units. - Introduced StockKeepingUnitsComponent for listing stock keeping units with pagination. - Integrated form fields for code, name, VAT, and SKU type in the stock keeping unit form. - Created API routes for stock keeping units with CRUD operations. - Updated input components to handle numeric and price types with Persian/Arabic digit normalization. - Enhanced key-value component to support tag display instead of badge. - Adjusted layout styles for sidebar menu. - Added AGENT.md for repository-specific coding guidelines and practices. - Implemented good management form with file upload and category selection.
This commit is contained in:
@@ -4,7 +4,12 @@ import { InputComponent } from '../input/input.component';
|
||||
|
||||
@Component({
|
||||
selector: 'field-vat',
|
||||
template: `<app-input label="مالیات بر ارزش افزوده" [control]="control" [name]="name" type="price" />`,
|
||||
template: `<app-input
|
||||
label="مالیات بر ارزش افزوده"
|
||||
[control]="control"
|
||||
[name]="name"
|
||||
type="number"
|
||||
/>`,
|
||||
imports: [ReactiveFormsModule, InputComponent],
|
||||
})
|
||||
export class VatComponent {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<shared-dialog [header]="preparedTitle" [(visible)]="visible" [modal]="true" [style]="{ width: '500px' }"
|
||||
[closable]="true">
|
||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||
@if (visible) {
|
||||
<app-shared-upload-file accept="image/*" name="image" [initial_file_url]="initialValues?.image_url || ''"
|
||||
(onSelect)="changeFile($event)" />
|
||||
}
|
||||
<field-name [control]="form.controls.name" name="name" />
|
||||
<field-sku label="شناسه کالا" [control]="form.controls.sku_id" name="sku_id" />
|
||||
<catalog-guild-goodCategories-select [guildId]="guildId" label="دستهبندی" [control]="form.controls.category_id"
|
||||
name="category_id" />
|
||||
<catalog-measure-units-select label="واحد اندازهگیری" [control]="form.controls.measure_unit_id"
|
||||
name="measure_unit_id" />
|
||||
<app-enum-select type="goodPricingModel" [control]="form.controls.pricing_model" name="pricing_model" />
|
||||
<field-description [control]="form.controls.description" />
|
||||
|
||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
||||
</form>
|
||||
</shared-dialog>
|
||||
@@ -0,0 +1,76 @@
|
||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||
import { DescriptionComponent, NameComponent, SkuComponent } from '@/shared/components';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { Component, Input, signal } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { Maybe } from '@/core';
|
||||
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
||||
import { CatalogGuildGoodCategoriesSelectComponent } from '@/shared/catalog/guildGoodCategories';
|
||||
import { CatalogMeasureUnitsSelectComponent } from '@/shared/catalog/measureUnits';
|
||||
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
||||
import { onSelectFileArgs } from '@/shared/components/uploadFile/model';
|
||||
import { fieldControl } from '@/shared/constants';
|
||||
import { buildFormData } from '@/utils';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SharedUploadFileComponent } from '../uploadFile/upload-file.component';
|
||||
import { IGoodRequestPayload, IGoodResponse } from './types';
|
||||
|
||||
@Component({
|
||||
selector: 'shared-good-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
SharedDialogComponent,
|
||||
FormFooterActionsComponent,
|
||||
EnumSelectComponent,
|
||||
SkuComponent,
|
||||
CatalogMeasureUnitsSelectComponent,
|
||||
NameComponent,
|
||||
DescriptionComponent,
|
||||
SharedUploadFileComponent,
|
||||
CatalogGuildGoodCategoriesSelectComponent,
|
||||
],
|
||||
})
|
||||
export class SharedGoodFormComponent extends AbstractFormDialog<
|
||||
IGoodRequestPayload,
|
||||
IGoodResponse
|
||||
> {
|
||||
@Input({ required: true }) guildId!: string;
|
||||
@Input({ required: true }) createFn!: (payload: FormData) => Observable<IGoodResponse>;
|
||||
@Input({ required: true }) updateFn!: (payload: FormData) => Observable<IGoodResponse>;
|
||||
|
||||
goodImage = signal<Maybe<File>>(null);
|
||||
|
||||
form = this.fb.group({
|
||||
name: fieldControl.name(this.initialValues?.name || '', true),
|
||||
sku_id: fieldControl.sku(this.initialValues?.sku.id || '', true),
|
||||
measure_unit_id: fieldControl.measure_unit(this.initialValues?.measure_unit.id || '', true),
|
||||
pricing_model: fieldControl.pricing_model(this.initialValues?.pricing_model || '', true),
|
||||
category_id: fieldControl.category_id(this.initialValues?.category.id || '', true),
|
||||
description: fieldControl.description(this.initialValues?.description || '', false),
|
||||
});
|
||||
|
||||
get preparedTitle() {
|
||||
return `${this.editMode ? 'ویرایش' : 'افزودن'} کالا`;
|
||||
}
|
||||
|
||||
changeFile(payload: onSelectFileArgs) {
|
||||
this.goodImage.set(payload.file);
|
||||
}
|
||||
|
||||
override ngOnChanges(): void {
|
||||
this.form.patchValue(this.initialValues || {});
|
||||
this.form.controls.category_id.setValue(this.initialValues?.category.id || '');
|
||||
this.form.controls.measure_unit_id.setValue(this.initialValues?.measure_unit.id || '');
|
||||
this.form.controls.sku_id.setValue(this.initialValues?.sku.id || '');
|
||||
}
|
||||
|
||||
override submitForm() {
|
||||
const formData = buildFormData(this.form, { image: this.goodImage() });
|
||||
if (this.editMode) {
|
||||
return this.updateFn(formData);
|
||||
}
|
||||
return this.createFn(formData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './form.component';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,23 @@
|
||||
import ISummary from '@/core/models/summary';
|
||||
|
||||
export interface IGoodResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
image_url: string;
|
||||
sku: ISummary;
|
||||
category: ISummary;
|
||||
measure_unit: ISummary;
|
||||
pricing_model: string;
|
||||
description?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface IGoodRequestPayload {
|
||||
name: string;
|
||||
sku_id: string;
|
||||
category_id: string;
|
||||
measure_unit_id: string;
|
||||
pricing_model: string;
|
||||
description?: string;
|
||||
}
|
||||
@@ -67,6 +67,7 @@ export class InputComponent {
|
||||
@Input() maxLength?: number;
|
||||
@Input() min?: number;
|
||||
@Input() max?: number;
|
||||
@Input() fixed?: number;
|
||||
@Output() valueChange = new EventEmitter<string | number>();
|
||||
@Output() blur = new EventEmitter<void>();
|
||||
|
||||
@@ -111,12 +112,13 @@ export class InputComponent {
|
||||
|
||||
get inputMode(): string | null {
|
||||
switch (this.type) {
|
||||
case 'price':
|
||||
return 'decimal';
|
||||
case 'number':
|
||||
case 'mobile':
|
||||
case 'phone':
|
||||
case 'postalCode':
|
||||
case 'nationalId':
|
||||
case 'price':
|
||||
case 'number':
|
||||
return 'numeric';
|
||||
default:
|
||||
return 'text';
|
||||
@@ -169,11 +171,12 @@ export class InputComponent {
|
||||
return 'email';
|
||||
case 'mobile':
|
||||
case 'phone':
|
||||
case 'price':
|
||||
case 'postalCode':
|
||||
case 'nationalId':
|
||||
case 'number':
|
||||
return 'number';
|
||||
case 'price':
|
||||
case 'number':
|
||||
return 'text';
|
||||
case 'checkbox':
|
||||
return 'checkbox';
|
||||
case 'switch':
|
||||
@@ -190,59 +193,98 @@ export class InputComponent {
|
||||
onPriceInput($event: InputNumberInputEvent) {
|
||||
this.onInput($event.originalEvent, true);
|
||||
}
|
||||
|
||||
private toEnglishDigits(value: string): string {
|
||||
return value
|
||||
.replace(/[۰-۹]/g, (digit) => String(digit.charCodeAt(0) - 1776))
|
||||
.replace(/[٠-٩]/g, (digit) => String(digit.charCodeAt(0) - 1632));
|
||||
}
|
||||
|
||||
private sanitizeNumericValue(value: string, allowDecimal: boolean): string {
|
||||
const normalized = this.toEnglishDigits(value).replace(/,/g, '');
|
||||
const cleaned = normalized.replace(allowDecimal ? /[^0-9.]/g : /[^0-9]/g, '');
|
||||
if (!allowDecimal) return cleaned;
|
||||
|
||||
const [integerPart = '', ...decimalParts] = cleaned.split('.');
|
||||
const mergedDecimals = decimalParts.join('');
|
||||
return decimalParts.length ? `${integerPart}.${mergedDecimals}` : integerPart;
|
||||
}
|
||||
|
||||
private applyFixed(value: string): string {
|
||||
if (this.fixed === undefined || this.fixed === null || this.fixed < 0 || value === '') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (this.fixed === 0) {
|
||||
return value.split('.')[0] || '0';
|
||||
}
|
||||
|
||||
const [integerPart = '0', decimalPart = ''] = value.split('.');
|
||||
const normalizedInteger = integerPart === '' ? '0' : integerPart;
|
||||
const fixedDecimal = decimalPart.slice(0, this.fixed).padEnd(this.fixed, '0');
|
||||
return `${normalizedInteger}.${fixedDecimal}`;
|
||||
}
|
||||
|
||||
onInput($event: Event, isPriceFormat?: boolean) {
|
||||
// @ts-ignore
|
||||
let value = $event.target.value as string;
|
||||
// @ts-ignore
|
||||
const isDotInput = $event.data === '.';
|
||||
const target = $event.target as HTMLInputElement | null;
|
||||
if (!target) return;
|
||||
|
||||
if (isPriceFormat) {
|
||||
value = value.replace(/,/g, '');
|
||||
let value = target.value ?? '';
|
||||
const allowDecimal = this.type === 'price' || this.type === 'number';
|
||||
|
||||
if (this.inputMode === 'numeric' || this.inputMode === 'decimal' || isPriceFormat) {
|
||||
value = this.sanitizeNumericValue(value, allowDecimal);
|
||||
value = this.applyFixed(value);
|
||||
}
|
||||
|
||||
let replacedTargetValue: Maybe<number | string> = null;
|
||||
if (this.inputMode === 'numeric' && !isDotInput) {
|
||||
let newValue = parseFloat(value || '0');
|
||||
const minValidator = (this.min || this.min === 0) && parseFloat(value) < this.min!;
|
||||
const maxValidator = (this.max || this.max === 0) && parseFloat(value) > this.max;
|
||||
if (minValidator || maxValidator) {
|
||||
if (maxValidator) {
|
||||
this.toastService.warn({
|
||||
text: `مقدار ${this.label} نمیتواند بیشتر از ${this.max} باشد.`,
|
||||
});
|
||||
}
|
||||
if (minValidator) {
|
||||
this.toastService.warn({
|
||||
text: `مقدار ${this.label} نمیتواند کمتر از ${this.min} باشد.`,
|
||||
});
|
||||
}
|
||||
console.log(value);
|
||||
|
||||
newValue = parseFloat(value.slice(0, value.length - 1));
|
||||
if (newValue < 0 || isNaN(newValue)) {
|
||||
newValue = 0;
|
||||
}
|
||||
if (this.preparedMaxLength && value.length > this.preparedMaxLength) {
|
||||
value = value.slice(0, this.preparedMaxLength);
|
||||
}
|
||||
|
||||
replacedTargetValue = isPriceFormat ? newValue.toLocaleString('en-US') : newValue;
|
||||
this.control.setValue(newValue);
|
||||
let numericValue = Number.isNaN(parseFloat(value)) ? 0 : parseFloat(value);
|
||||
const minValidator = (this.min || this.min === 0) && numericValue < this.min!;
|
||||
const maxValidator = (this.max || this.max === 0) && numericValue > this.max!;
|
||||
if (
|
||||
(this.inputMode === 'numeric' || this.inputMode === 'decimal') &&
|
||||
value !== '' &&
|
||||
(minValidator || maxValidator)
|
||||
) {
|
||||
if (maxValidator) {
|
||||
this.toastService.warn({
|
||||
text: `مقدار ${this.label} نمیتواند بیشتر از ${this.max} باشد.`,
|
||||
});
|
||||
}
|
||||
if (!['nationalId', 'phone', 'postalCode', 'mobile'].includes(this.type)) {
|
||||
this.control.setValue(newValue);
|
||||
if (minValidator) {
|
||||
this.toastService.warn({
|
||||
text: `مقدار ${this.label} نمیتواند کمتر از ${this.min} باشد.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.preparedMaxLength && this.preparedMaxLength < value.length) {
|
||||
let newValue = value.slice(0, value.length - 1);
|
||||
// @ts-ignore
|
||||
replacedTargetValue = newValue;
|
||||
this.control.setValue(newValue);
|
||||
value = value.slice(0, -1);
|
||||
numericValue = Number.isNaN(parseFloat(value)) ? 0 : parseFloat(value);
|
||||
}
|
||||
|
||||
if (replacedTargetValue !== null) {
|
||||
console.log(replacedTargetValue);
|
||||
const isIdentifierField = [
|
||||
'nationalId',
|
||||
'phone',
|
||||
'postalCode',
|
||||
'mobile',
|
||||
'email',
|
||||
'password',
|
||||
'simple',
|
||||
].includes(this.type);
|
||||
this.control.setValue(isIdentifierField ? value : value === '' ? '' : numericValue);
|
||||
|
||||
// @ts-ignore
|
||||
$event.value = replacedTargetValue;
|
||||
// @ts-ignore
|
||||
$event.target.value = replacedTargetValue;
|
||||
const viewValue = isPriceFormat && value !== '' ? numericValue.toLocaleString('en-US') : value;
|
||||
console.log('viewValue', viewValue);
|
||||
|
||||
target.value = viewValue;
|
||||
if (
|
||||
(this.inputMode === 'numeric' || this.inputMode === 'decimal') &&
|
||||
this.valueChange.observed
|
||||
) {
|
||||
this.valueChange.emit(isIdentifierField ? value : numericValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
}"
|
||||
>
|
||||
<ng-content>
|
||||
@if (valueType() === "badge") {
|
||||
<p-badge [value]="valueToShow()?.toString()" [severity]="value ? 'success' : 'danger'" />
|
||||
@if (valueType() === "tag") {
|
||||
<p-tag [value]="valueToShow()?.toString()" [severity]="value ? 'contrast' : 'danger'" />
|
||||
} @else {
|
||||
<span class="text-text-color text-base font-bold grow"> {{ valueToShow() }}</span>
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ import { formatDurationToText, formatJalali, JALALI_DATE_FORMATS } from '@/utils
|
||||
import priceMaskUtils from '@/utils/price-mask.utils';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, Input, signal } from '@angular/core';
|
||||
import { Badge } from 'primeng/badge';
|
||||
import { Tag } from 'primeng/tag';
|
||||
import { TDataType } from '../pageDataList/page-data-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-key-value',
|
||||
standalone: true,
|
||||
imports: [CommonModule, Badge],
|
||||
imports: [CommonModule, Tag],
|
||||
templateUrl: './key-value.component.html',
|
||||
host: {
|
||||
class: 'w-full block',
|
||||
@@ -18,24 +19,12 @@ export class KeyValueComponent {
|
||||
@Input() value?: string | boolean | number;
|
||||
@Input() hint?: string;
|
||||
@Input() alignment?: 'start' | 'center' | 'end' = 'start';
|
||||
@Input() type:
|
||||
| 'price'
|
||||
| 'active'
|
||||
| 'boolean'
|
||||
| 'has'
|
||||
| 'date'
|
||||
| 'dateTime'
|
||||
| 'duration'
|
||||
| 'id'
|
||||
| 'thumbnail'
|
||||
| 'nested'
|
||||
| 'index'
|
||||
| 'number'
|
||||
| 'simple' = 'simple';
|
||||
@Input() type: TDataType = 'simple';
|
||||
|
||||
@Input() trueValueToShow?: string;
|
||||
@Input() falseValueToShow?: string;
|
||||
@Input() variant?: 'badge' | 'text';
|
||||
@Input() variant?: 'tag' | 'text';
|
||||
tagSeverity = computed(() => (this.value ? 'success' : 'danger'));
|
||||
|
||||
valueToShow = signal(this.setValueToShow());
|
||||
|
||||
@@ -105,6 +94,6 @@ export class KeyValueComponent {
|
||||
}
|
||||
|
||||
valueType = computed(() =>
|
||||
this.variant || ['boolean', 'has', 'active'].includes(this.type) ? 'badge' : 'text',
|
||||
this.variant || ['boolean', 'has', 'active'].includes(this.type) ? 'tag' : 'text',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="listKeyValue">
|
||||
@for (col of columns; track `gridView_${col.field.toString()}_${$index}`) {
|
||||
@if (col.type !== "index") {
|
||||
<app-key-value [label]="col.header">
|
||||
<app-key-value [label]="col.header" [variant]="col.variant">
|
||||
@if (col.type === "thumbnail") {
|
||||
<div
|
||||
class="w-20 h-20 rounded-2xl overflow-hidden bg-surface-50 cursor-pointer"
|
||||
@@ -27,6 +27,8 @@
|
||||
<img [src]="item[col.field]" class="w-full h-full object-cover" />
|
||||
}
|
||||
</div>
|
||||
} @else if (col.variant === "tag") {
|
||||
<p-tag [value]="getCell(item, col)" [severity]="col.tagOptions?.severity || 'contrast'"></p-tag>
|
||||
} @else if (getTemplate(col)) {
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="getTemplate(col)"
|
||||
|
||||
@@ -17,42 +17,9 @@ import { DrawerModule } from 'primeng/drawer';
|
||||
import { PaginatorModule } from 'primeng/paginator';
|
||||
import { SkeletonModule } from 'primeng/skeleton';
|
||||
import { TableModule } from 'primeng/table';
|
||||
import { Tag } from 'primeng/tag';
|
||||
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);
|
||||
}
|
||||
import { IColumn } from './page-data-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-page-data-list-grid-view',
|
||||
@@ -69,6 +36,7 @@ export interface IColumn<T = any> {
|
||||
DrawerModule,
|
||||
UikitCopyComponent,
|
||||
KeyValueComponent,
|
||||
Tag,
|
||||
],
|
||||
})
|
||||
export class AppPageDataListGridView<I = any> {
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
|
||||
<ng-template #body let-item let-i="rowIndex" let-expanded="expanded">
|
||||
<tr>
|
||||
<td>
|
||||
@if (showIndex) {
|
||||
@if (showIndex) {
|
||||
<td>
|
||||
{{ i * (currentPage || 1) + 1 }}
|
||||
}
|
||||
</td>
|
||||
</td>
|
||||
}
|
||||
@for (col of columns; track col.field) {
|
||||
<td>
|
||||
@if (col.type === "thumbnail") {
|
||||
@@ -59,6 +59,8 @@
|
||||
<img [src]="item[col.field]" class="w-full h-full object-cover" />
|
||||
}
|
||||
</div>
|
||||
} @else if (col.variant === "tag") {
|
||||
<p-tag [value]="getCell(item, col)" [severity]="col.tagOptions?.severity || 'contrast'"></p-tag>
|
||||
} @else if (getTemplate(col)) {
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="getTemplate(col)"
|
||||
@@ -190,6 +192,7 @@
|
||||
[label]="col.header"
|
||||
[value]="item[expandableItemKey][col.field]"
|
||||
[type]="col.type || 'simple'"
|
||||
[variant]="col.variant"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
@@ -211,6 +214,11 @@
|
||||
<ng-template #loadingbody let-columns>
|
||||
@for (i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; track i) {
|
||||
<tr style="height: 46px">
|
||||
@if (showIndex) {
|
||||
<td>
|
||||
<p-skeleton></p-skeleton>
|
||||
</td>
|
||||
}
|
||||
@for (col of columns; track col) {
|
||||
<td>
|
||||
<p-skeleton></p-skeleton>
|
||||
|
||||
@@ -17,43 +17,10 @@ import { DrawerModule } from 'primeng/drawer';
|
||||
import { PaginatorModule } from 'primeng/paginator';
|
||||
import { SkeletonModule } from 'primeng/skeleton';
|
||||
import { TableModule } from 'primeng/table';
|
||||
import { Tag } from 'primeng/tag';
|
||||
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);
|
||||
}
|
||||
import { IColumn } from './page-data-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-page-data-list-table-view',
|
||||
@@ -72,6 +39,7 @@ export interface IColumn<T = any> {
|
||||
DrawerModule,
|
||||
UikitCopyComponent,
|
||||
KeyValueComponent,
|
||||
Tag,
|
||||
],
|
||||
})
|
||||
export class AppPageDataListTableView<I = any> {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
[showEdit]="showEdit"
|
||||
[showDelete]="showDelete"
|
||||
[showDetails]="showDetails"
|
||||
[showIndex]="showIndex"
|
||||
(onEdit)="edit($event)"
|
||||
(onDelete)="remove($event)"
|
||||
(onDetails)="details($event)"
|
||||
|
||||
@@ -22,7 +22,7 @@ import { TableModule } from 'primeng/table';
|
||||
import { AppPageDataListGridView } from './page-data-list-grid-view.component';
|
||||
import { AppPageDataListTableView } from './page-data-list-table-view.component';
|
||||
|
||||
type TDataType =
|
||||
export type TDataType =
|
||||
| 'simple'
|
||||
| 'price'
|
||||
| 'boolean'
|
||||
@@ -31,7 +31,13 @@ type TDataType =
|
||||
| 'index'
|
||||
| 'id'
|
||||
| 'thumbnail'
|
||||
| 'number';
|
||||
| 'badge'
|
||||
| 'number'
|
||||
| 'dateTime'
|
||||
| 'duration'
|
||||
| 'active'
|
||||
| 'has';
|
||||
|
||||
export interface IColumn<T = any> {
|
||||
field: T extends object ? keyof T | string : string;
|
||||
header: string;
|
||||
@@ -39,6 +45,11 @@ export interface IColumn<T = any> {
|
||||
minWidth?: string;
|
||||
canCopy?: boolean;
|
||||
type?: TDataType;
|
||||
variant?: 'text' | 'tag';
|
||||
|
||||
tagOptions?: {
|
||||
severity?: 'success' | 'secondary' | 'info' | 'warn' | 'danger' | 'contrast';
|
||||
};
|
||||
|
||||
nestedOption?: {
|
||||
path: string;
|
||||
@@ -105,6 +116,7 @@ export class PageDataListComponent<I = any> {
|
||||
@Input() expandableItemKey?: string = '';
|
||||
@Input() expandColumns?: Maybe<IColumn[]> = null;
|
||||
@Input() dataKey?: string = 'items';
|
||||
@Input() showIndex?: boolean = true;
|
||||
|
||||
@ContentChild('filter', { static: true }) filter!: TemplateRef<any> | null;
|
||||
@ContentChild('caption', { static: true }) caption!: TemplateRef<any> | null;
|
||||
|
||||
Reference in New Issue
Block a user