feat: add logo image and RTL support styles

- Added logo image to assets.
- Implemented RTL support styles in rtlSupport.scss for various components including breadcrumb, datatable, and tree node toggle button.

chore: configure environment files for production, staging, and development

- Created environment.prod.ts for production settings.
- Created environment.staging.ts for staging settings.
- Created environment.ts for local development settings.

feat: define custom theme preset

- Added presets.ts to define a custom theme preset using PrimeUIX with specific component styles and semantic colors.

chore: set up proxy configuration for local development
This commit is contained in:
2025-12-04 21:07:18 +03:30
parent c58210cdbd
commit 07fec02ea1
164 changed files with 20175 additions and 735 deletions
@@ -0,0 +1,30 @@
<uikit-field [label]="label" [name]="name" [control]="control">
<div class="grid grid-cols-2 items-center gap-4">
<p-autoComplete
[formControl]="hoursCtrl"
[suggestions]="filteredHours"
(completeMethod)="filterHours($event)"
[dropdown]="true"
[forceSelection]="false"
placeholder="ساعت"
inputClass="w-full"
type="number"
></p-autoComplete>
<p-autoComplete
[formControl]="minutesCtrl"
[suggestions]="filteredMinutes"
(completeMethod)="filterMinutes($event)"
[dropdown]="true"
[forceSelection]="false"
placeholder="دقیقه"
type="number"
inputClass="w-full"
></p-autoComplete>
</div>
@if (hoursCtrl.value || minutesCtrl.value) {
<span class="text-xs mt-1 text-muted-color">
مدت زمان آزمون را {{ hoursCtrl.value || 0 }} ساعت و {{ minutesCtrl.value || 0 }} دقیقه انتخاب کردید.
</span>
}
</uikit-field>
@@ -0,0 +1,93 @@
import { UikitFieldComponent } from '@/uikit/uikit-field.component';
import { CommonModule } from '@angular/common';
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { AutoCompleteModule } from 'primeng/autocomplete';
@Component({
selector: 'uikit-duration',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, AutoCompleteModule, UikitFieldComponent],
templateUrl: './duration-picker.component.html',
})
export class UikitDurationPickerComponent implements OnInit {
@Input() label = 'مدت زمان';
@Input() name = 'duration';
@Input() control!: FormControl<string | null>;
hoursCtrl = new FormControl<string | null>('0');
minutesCtrl = new FormControl<string | null>('0');
hoursOptions: string[] = Array.from({ length: 24 }, (_, i) => String(i));
minutesOptions: string[] = ['0', '5', '10', '15', '20', '30', '45'];
filteredHours: string[] = [];
filteredMinutes: string[] = [];
ngOnInit(): void {
// initialize from parent control if present
if (this.control) {
const total = Number(this.control.value) || 0;
const h = Math.floor(total / 60);
const m = total % 60;
this.hoursCtrl.setValue(String(h));
this.minutesCtrl.setValue(String(m));
this.hoursCtrl.valueChanges.subscribe(() => {
if (this.toNumberSafe(this.hoursCtrl.value) > 23) {
this.hoursCtrl.setValue('23', { emitEvent: false });
}
this.syncToParent();
});
this.minutesCtrl.valueChanges.subscribe(() => {
if (this.toNumberSafe(this.minutesCtrl.value) > 59) {
this.minutesCtrl.setValue('59', { emitEvent: false });
}
this.syncToParent();
});
this.control.valueChanges.subscribe((v) => this.syncFromParent(v));
} else {
this.hoursCtrl.valueChanges.subscribe(() => this.syncToParent());
this.minutesCtrl.valueChanges.subscribe(() => this.syncToParent());
}
}
private toNumberSafe(s: string | null | undefined) {
if (s == null || s === '') return 0;
const n = Number(s);
return Number.isFinite(n) ? Math.max(0, Math.trunc(n)) : 0;
}
syncToParent() {
if (!this.control) return;
const h = this.toNumberSafe(this.hoursCtrl.value);
const m = this.toNumberSafe(this.minutesCtrl.value);
const total = h * 60 + m;
// write numeric minutes into parent control only when changed
const current = this.control.value;
if (String(current) !== String(total)) {
this.control.setValue(String(total), { emitEvent: false });
}
}
syncFromParent(v: any) {
const total = Number(v) || 0;
const h = Math.floor(total / 60);
const m = total % 60;
if (String(h) !== this.hoursCtrl.value)
this.hoursCtrl.setValue(String(h), { emitEvent: false });
if (String(m) !== this.minutesCtrl.value)
this.minutesCtrl.setValue(String(m), { emitEvent: false });
}
filterHours(event: { query: string }) {
const q = (event.query || '').toString();
this.filteredHours = this.hoursOptions.filter((x) => x.startsWith(q));
}
filterMinutes(event: { query: string }) {
const q = (event.query || '').toString();
this.filteredMinutes = this.minutesOptions.filter((x) => x.startsWith(q));
}
}