Files
portal/app/resources/js/components/settings/security/SessionsTable.vue
T
Дмитрий 849bc73290 refactor(frontend): Sprint 4 Phase B/2 — split 3 user views (audit O-refactor-04 хвост)
BillingView 416→114 (+ BalanceCard 155 + TransactionsTable 113 + InvoicesTable 90
+ billingFormatters 51 composable: formatPlain/formatCost/statusChipColor/
statusLabel/formatLabel/formatIcon/txAmountClass).
SecurityTab 354→39 (+ ChangePasswordCard 17 + TwoFactorCard 218 + RecoveryCodesCard 104
+ SessionsTable 66; auth-store читается напрямую в каждом sub-component).
RemindersView 345→183 (+ RemindersFilters 51 + RemindersList 173;
ReminderDialog уже отдельный с прошлой фазы — служит как ReminderForm).

State (`activeTab`, `editingReminder`, `deletingReminderId` в RemindersView)
остаётся в parent ради единого reload-flow + confirm-dialog'ов. Auth-store
читается напрямую в TwoFactorCard/RecoveryCodesCard через useAuthStore() —
без prop-drilling. Reminders-store читается напрямую в RemindersFilters/
RemindersList.

Все sub-components <250 строк (acceptance threshold). 3 view-shells: 114/39/183.

Регрессия: ESLint 0 + vue-tsc 0 + Vitest 416/416 + build OK 968 ms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:46:14 +03:00

67 lines
2.3 KiB
Vue

<script setup lang="ts">
/**
* SessionsTable — список активных сессий с кнопкой «Завершить» для не-текущей сессии.
* Sprint 4 Phase B/2 — split SecurityTab (audit O-refactor-04 хвост).
*
* MVP: статичный mock из 3 строк, soft-revoke не подключён к API. Реальный flow:
* GET /api/account/sessions — список.
* DELETE /api/account/sessions/{id} — revoke.
*/
interface Session {
device: string;
location: string;
when: string;
current: boolean;
}
const sessions: Session[] = [
{ device: 'Chrome 138, Windows', location: 'Москва (10.0.20.5)', when: 'сейчас', current: true },
{ device: 'Safari, iPhone 16', location: 'Москва · 14:21', when: '15 мин назад', current: false },
{ device: 'Firefox 132, Linux', location: 'Санкт-Петербург · вчера 22:08', when: '14 ч назад', current: false },
];
</script>
<template>
<v-card variant="outlined" class="pa-4">
<h3 class="text-subtitle-2 mb-3">Активные сессии ({{ sessions.length }})</h3>
<ul class="sessions-list">
<li v-for="(s, i) in sessions" :key="i" class="session-row">
<div class="session-info">
<div class="session-device">
{{ s.device }}
<v-chip v-if="s.current" color="primary" size="x-small" variant="tonal" class="ml-2">
эта сессия
</v-chip>
</div>
<div class="session-meta text-caption text-medium-emphasis">
{{ s.location }} · {{ s.when }}
</div>
</div>
<v-btn v-if="!s.current" variant="text" size="small" color="error"> Завершить </v-btn>
</li>
</ul>
</v-card>
</template>
<style scoped>
.sessions-list {
list-style: none;
padding: 0;
margin: 0;
}
.session-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #f0ede4;
}
.session-row:last-child {
border-bottom: none;
}
.session-device {
font-weight: 500;
color: #081319;
}
</style>