c5242271d7
Closes Audit #3 P3 batch. Changes: 1. **knip.config.ts cleanup** — remove 4 stale config hints flagged in Audit #3 Phase 1B (`ignore: tests/**` redundant since `project` is `resources/js/**`; `ignoreDependencies` for vitest/@vue/test-utils/jsdom redundant since knip auto-detects test frameworks). Add `histoire.config.ts` + `resources/js/histoire.setup.ts` to entry — closes 2 documented FPs (histoire.setup.ts + @histoire/plugin-vue unused-flag). Verified: `npx knip` exits 0 clean. 2. **Admin table actions column header label** — change `title: ''` → `title: 'Действия'` in: - TenantsTable.vue (actions column, /admin/tenants) - AdminSupplierPricesView.vue (actions column, /admin/supplier-prices) Closes axe-core `empty-table-header` violation seen in Audit #3 Phase 7 on /admin/tenants. Header is now visible in UI (better UX than sr-only sleight-of-hand). 3. **npm overrides for lodash** in `package.json` — pin `pa11y-ci > lodash` to ^4.17.21. Verified: `npm ls lodash` resolves to lodash@4.17.23 (latest 4.x; CVE-2021-23337 + GHSA-f23m patched in <4.17.21, our version is above that). npm audit may still surface advisory ranges as informational. 4. **Decision doc for pgFormatter (Q.HARD.002)** — explicit FIX-DEFER with 3-hypothesis comparison (Strawberry Perl install vs sqlfluff replacement vs Docker pg_format vs drop SQL formatting). Decision: drop automated SQL formatting until Б-1 closure; squawk (linter) covers correctness. Addendum: axe-core .v-overlay-container region landmark — no permanent axe-core test setup exists, so no whitelist needed at this point. Verification: - knip: 0 issues - vue-tsc: 0 errors - ESLint: 0 errors - Vitest: 91 files / 736 passed / 3 skipped (no regressions) - Vite build: 2.03s Plan: docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md Task 4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
114 lines
3.7 KiB
Vue
114 lines
3.7 KiB
Vue
<template>
|
|
<div class="admin-supplier-prices-view">
|
|
<h1 class="text-h4 mb-6">Цены поставщиков (закупка)</h1>
|
|
<v-card elevation="1">
|
|
<v-data-table :headers="headers" :items="suppliers" density="comfortable" class="numeric-tnum">
|
|
<template #[`item.cost_rub`]="{ item }">
|
|
<v-text-field
|
|
v-model="item.cost_rub"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
density="compact"
|
|
hide-details
|
|
variant="plain"
|
|
:aria-label="`Cost (₽) для ${item.name}`"
|
|
/>
|
|
</template>
|
|
<template #[`item.quality_score`]="{ item }">
|
|
<v-text-field
|
|
v-model="item.quality_score"
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
max="9.99"
|
|
density="compact"
|
|
hide-details
|
|
variant="plain"
|
|
:aria-label="`Quality для ${item.name}`"
|
|
/>
|
|
</template>
|
|
<template #[`item.is_active`]="{ item }">
|
|
<v-switch
|
|
v-model="item.is_active"
|
|
hide-details
|
|
inset
|
|
density="compact"
|
|
:aria-label="`Active для ${item.name}`"
|
|
/>
|
|
</template>
|
|
<template #[`item.actions`]="{ item }">
|
|
<v-btn size="small" color="primary" :loading="!!saving[item.id]" @click="save(item)">
|
|
Сохранить
|
|
</v-btn>
|
|
</template>
|
|
</v-data-table>
|
|
</v-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, reactive } from 'vue';
|
|
import axios from 'axios';
|
|
|
|
/**
|
|
* SaaS-admin → Цены поставщиков (Plan 4 Task 10).
|
|
*
|
|
* Backend: AdminSuppliersController (GET/PATCH).
|
|
* Палитра Forest + JetBrains Mono для tnum-цифр.
|
|
*
|
|
* defineExpose ниже — для Vitest unit-тестов (`load`/`save`/`suppliers`/
|
|
* `saving`). На прод-сборку это не влияет.
|
|
*/
|
|
|
|
interface SupplierRow {
|
|
id: number;
|
|
code: string;
|
|
name: string;
|
|
cost_rub: string;
|
|
quality_score: string;
|
|
is_active: boolean;
|
|
}
|
|
|
|
const suppliers = ref<SupplierRow[]>([]);
|
|
const saving = reactive<Record<number, boolean>>({});
|
|
|
|
const headers = [
|
|
{ title: 'Code', key: 'code', sortable: false, width: 80 },
|
|
{ title: 'Name', key: 'name', sortable: false },
|
|
{ title: 'Cost (₽)', key: 'cost_rub', sortable: false, width: 140 },
|
|
{ title: 'Quality', key: 'quality_score', sortable: false, width: 100 },
|
|
{ title: 'Active', key: 'is_active', sortable: false, width: 100 },
|
|
{ title: 'Действия', key: 'actions', sortable: false, width: 120 },
|
|
];
|
|
|
|
async function load(): Promise<void> {
|
|
const { data } = await axios.get('/api/admin/suppliers');
|
|
suppliers.value = data.data;
|
|
}
|
|
|
|
async function save(s: SupplierRow): Promise<void> {
|
|
saving[s.id] = true;
|
|
try {
|
|
await axios.patch(`/api/admin/suppliers/${s.id}`, {
|
|
cost_rub: s.cost_rub,
|
|
quality_score: s.quality_score,
|
|
is_active: s.is_active,
|
|
});
|
|
} finally {
|
|
saving[s.id] = false;
|
|
}
|
|
}
|
|
|
|
onMounted(load);
|
|
|
|
defineExpose({ load, save, suppliers, saving });
|
|
</script>
|
|
|
|
<style scoped>
|
|
.numeric-tnum :deep(td) {
|
|
font-feature-settings: 'tnum';
|
|
font-family: 'JetBrains Mono', monospace;
|
|
}
|
|
</style>
|