feat: client-side currency conversion with admin-configurable rates
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

- CurrencyRatesService: RUB-based rates, persisted via localStorage
- CurrencyConvertPipe: impure pipe converting item price to selected currency
- Admin settings page: editable currency rates form
- Applied conversion to product-card, product-information, quick-view-dialog,
  delivery-information, compare-table, cart totals + payment payload

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-08-15 01:45:12 +04:00
parent d8c078ad5a
commit 4510eb769a
20 changed files with 235 additions and 30 deletions

View File

@@ -11,4 +11,25 @@
<span>{{ 'adminSettings.densityCompact' | translate }}</span>
</label>
</div>
<div class="settings-card">
<h2>{{ 'adminSettings.currencyRates' | translate }}</h2>
<p class="settings-explain">{{ 'adminSettings.currencyRatesExplain' | translate }}</p>
<div class="rate-row" *ngFor="let currency of languageService.currencies">
<span class="rate-code">{{ currency.code }}</span>
<input
class="rate-input"
type="number"
min="0"
step="0.0001"
[disabled]="currency.code === currencyRates.baseCurrency"
[ngModel]="rateDrafts()[currency.code]"
(ngModelChange)="onRateInput(currency.code, $event)"
/>
</div>
<div class="rate-actions">
<button type="button" class="save-button" (click)="saveRates()">{{ 'adminSettings.currencyRatesSave' | translate }}</button>
<span class="saved-message" *ngIf="showSavedMessage()">{{ 'adminSettings.currencyRatesSaved' | translate }}</span>
</div>
</div>
</section>

View File

@@ -12,3 +12,25 @@
.settings-explain { margin: 0; color: var(--text-secondary, #6b7280); font-size: var(--font-size-sm, 0.8125rem); }
.toggle-row { display: flex; align-items: center; gap: 8px; font-weight: var(--font-weight-normal, 400); }
.rate-row { display: flex; align-items: center; gap: 12px; }
.rate-code { width: 48px; font-weight: var(--font-weight-medium, 500); }
.rate-input {
width: 120px;
padding: 6px 8px;
border: 1px solid var(--border-color, #d3dad9);
border-radius: var(--radius-sm, 4px);
background: var(--bg-primary, #fff);
}
.rate-input:disabled { opacity: 0.6; }
.rate-actions { display: flex; align-items: center; gap: 12px; margin-top: 4px; }
.save-button {
padding: 8px 16px;
border: none;
border-radius: var(--radius-sm, 4px);
background: var(--color-primary, #16a34a);
color: #fff;
cursor: pointer;
}
.saved-message { color: var(--color-success, #16a34a); font-size: var(--font-size-sm, 0.8125rem); }

View File

@@ -1,21 +1,47 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AdminPreferencesService } from '../services/admin-preferences.service';
import { TranslatePipe } from '../../../../i18n/translate.pipe';
import { ToggleComponent } from '../../../../shared/ui/toggle/toggle.component';
import { CurrencyRatesService } from '../../../../services/currency-rates.service';
import { LanguageService } from '../../../../services/language.service';
const SAVED_MESSAGE_DURATION_MS = 2000;
@Component({
selector: 'app-admin-settings-page',
standalone: true,
imports: [FormsModule, TranslatePipe, ToggleComponent],
imports: [CommonModule, FormsModule, TranslatePipe, ToggleComponent],
templateUrl: './admin-settings-page.component.html',
styleUrls: ['./admin-settings-page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AdminSettingsPageComponent {
readonly preferences = inject(AdminPreferencesService);
readonly currencyRates = inject(CurrencyRatesService);
readonly languageService = inject(LanguageService);
readonly rateDrafts = signal<Record<string, number>>({ ...this.currencyRates.rates() });
readonly showSavedMessage = signal(false);
onCompactToggle(compact: boolean): void {
this.preferences.setDensity(compact ? 'compact' : 'comfortable');
}
onRateInput(code: string, value: string): void {
const parsed = Number(value);
this.rateDrafts.set({ ...this.rateDrafts(), [code]: parsed });
}
saveRates(): void {
for (const currency of this.languageService.currencies) {
const rate = this.rateDrafts()[currency.code];
if (Number.isFinite(rate) && rate > 0) {
this.currencyRates.setRate(currency.code, rate);
}
}
this.showSavedMessage.set(true);
setTimeout(() => this.showSavedMessage.set(false), SAVED_MESSAGE_DURATION_MS);
}
}