779 lines
21 KiB
Vue
779 lines
21 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, ref } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import StatusBadge from '@/components/ui/StatusBadge.vue'
|
||
import { checksApi, mapAdminOrder, ordersApi, type ApiOrder } from '@/services/api'
|
||
import { ApiError } from '@/services/http'
|
||
import { formatCurrency, formatDate } from '@/utils/format'
|
||
import { useToast } from '@/composables/useToast'
|
||
import { confirm } from '@/composables/useConfirm'
|
||
import { rawStatusLabel } from '@/utils/orderStatus'
|
||
import type { Check } from '@/types'
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const toast = useToast()
|
||
|
||
const activeTab = ref<'info' | 'history'>('info')
|
||
const order = ref<ApiOrder | null>(null)
|
||
const orderChecks = ref<Check[]>([])
|
||
const busyId = ref<string | null>(null)
|
||
const expandedId = ref<string | null>(null)
|
||
const previewImage = ref<string | null>(null)
|
||
|
||
const displayOrder = computed(() =>
|
||
order.value ? mapAdminOrder(order.value) : null,
|
||
)
|
||
|
||
const totalCheckAmount = computed(() =>
|
||
orderChecks.value.reduce((sum, c) => sum + c.amount, 0),
|
||
)
|
||
|
||
const pendingChecks = computed(() =>
|
||
orderChecks.value.filter((c) => c.status === 'pending_review'),
|
||
)
|
||
|
||
const allChecksApproved = computed(
|
||
() =>
|
||
orderChecks.value.length > 0 &&
|
||
orderChecks.value.every((c) => c.status === 'approved'),
|
||
)
|
||
|
||
const canFinalApprove = computed(() => {
|
||
if (!allChecksApproved.value || !order.value) return false
|
||
const status = order.value.status
|
||
return status === 'checks_approved' || status === 'awaiting_check_review'
|
||
})
|
||
|
||
const finalizing = ref(false)
|
||
|
||
const reviewHistory = computed(() => {
|
||
const items = order.value?.statusHistory ?? []
|
||
const checkRelated = new Set([
|
||
'check_in_progress',
|
||
'awaiting_check_review',
|
||
'check_needs_fix',
|
||
'check_rejected',
|
||
'checks_approved',
|
||
])
|
||
const filtered = items.filter(
|
||
(h) =>
|
||
checkRelated.has(h.toStatus) ||
|
||
(h.fromStatus && checkRelated.has(h.fromStatus)) ||
|
||
/چک/.test(h.note || '') ||
|
||
/چک/.test(h.reason || ''),
|
||
)
|
||
const source = filtered.length ? filtered : items
|
||
return [...source].sort(
|
||
(a, b) => new Date(b.changedAt).getTime() - new Date(a.changedAt).getTime(),
|
||
)
|
||
})
|
||
|
||
const checkReviewNotes = computed(() =>
|
||
orderChecks.value
|
||
.filter((c) => c.notes?.trim())
|
||
.map((c) => ({
|
||
id: c.id,
|
||
row: c.row,
|
||
status: c.status,
|
||
notes: c.notes!,
|
||
updatedAt: c.updatedAt,
|
||
})),
|
||
)
|
||
|
||
async function loadData() {
|
||
const orderId = String(route.params.id)
|
||
try {
|
||
const [orderData, checksData] = await Promise.all([
|
||
ordersApi.get(orderId),
|
||
checksApi.listByOrder(orderId),
|
||
])
|
||
order.value = orderData
|
||
orderChecks.value = checksData
|
||
if (checksData.length === 1) expandedId.value = checksData[0]!.id
|
||
} catch (err) {
|
||
toast.error(err instanceof ApiError ? err.message : 'بارگذاری چکها انجام نشد.')
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
void loadData()
|
||
})
|
||
|
||
function goBack() {
|
||
router.push('/orders')
|
||
}
|
||
|
||
function checkBadgeStatus(status: Check['status']) {
|
||
if (status === 'approved') return 'check_approved'
|
||
if (status === 'rejected') return 'check_rejected'
|
||
return 'pending_check_review'
|
||
}
|
||
|
||
function actorLabel(changedBy?: string) {
|
||
if (!changedBy) return '—'
|
||
if (changedBy === 'customer') return 'مشتری'
|
||
if (changedBy.startsWith('admin:')) return 'ادمین'
|
||
return changedBy
|
||
}
|
||
|
||
function formatDateTime(iso?: string) {
|
||
if (!iso) return '—'
|
||
try {
|
||
return new Intl.DateTimeFormat('fa-IR', {
|
||
dateStyle: 'medium',
|
||
timeStyle: 'short',
|
||
}).format(new Date(iso))
|
||
} catch {
|
||
return iso.slice(0, 16).replace('T', ' ')
|
||
}
|
||
}
|
||
|
||
function ownerName(check: Check) {
|
||
return [check.ownerFirstName, check.ownerLastName].filter(Boolean).join(' ')
|
||
}
|
||
|
||
function detailFields(check: Check) {
|
||
return [
|
||
{ label: 'بانک', value: check.bankName },
|
||
{ label: 'شعبه', value: check.branchName },
|
||
{ label: 'شناسه صیادی', value: check.sayadId, ltr: true },
|
||
{ label: 'شماره چک', value: check.checkNumber, ltr: true },
|
||
{ label: 'صاحب چک', value: ownerName(check) },
|
||
{ label: 'کد ملی', value: check.ownerNationalId, ltr: true },
|
||
{ label: 'یادداشت', value: check.notes },
|
||
].filter((f) => !!f.value?.trim())
|
||
}
|
||
|
||
function toggleExpand(check: Check) {
|
||
expandedId.value = expandedId.value === check.id ? null : check.id
|
||
}
|
||
|
||
async function applyCheckStatus(check: Check, status: 'approved' | 'rejected') {
|
||
busyId.value = check.id
|
||
try {
|
||
const updated = await checksApi.updateStatus(check.id, { status })
|
||
const index = orderChecks.value.findIndex((c) => c.id === check.id)
|
||
if (index !== -1) orderChecks.value[index] = updated
|
||
order.value = await ordersApi.get(String(route.params.id))
|
||
return true
|
||
} catch (err) {
|
||
toast.error(err instanceof ApiError ? err.message : 'بهروزرسانی وضعیت چک انجام نشد.')
|
||
return false
|
||
} finally {
|
||
busyId.value = null
|
||
}
|
||
}
|
||
|
||
async function updateCheckStatus(check: Check, status: Check['status']) {
|
||
if (status !== 'approved' && status !== 'rejected') return
|
||
|
||
const amountLabel = formatCurrency(check.amount)
|
||
const ok = await confirm(
|
||
status === 'approved'
|
||
? {
|
||
title: 'تأیید چک',
|
||
message: `آیا از تأیید چک ردیف ${check.row} به مبلغ ${amountLabel} مطمئن هستید؟`,
|
||
tone: 'primary',
|
||
confirmLabel: 'تأیید چک',
|
||
cancelLabel: 'انصراف',
|
||
}
|
||
: {
|
||
title: 'رد چک',
|
||
message: `آیا از رد چک ردیف ${check.row} به مبلغ ${amountLabel} مطمئن هستید؟`,
|
||
tone: 'danger',
|
||
confirmLabel: 'رد چک',
|
||
cancelLabel: 'انصراف',
|
||
},
|
||
)
|
||
if (!ok) return
|
||
|
||
const success = await applyCheckStatus(check, status)
|
||
if (success) {
|
||
toast.success(status === 'approved' ? 'چک تأیید شد.' : 'چک رد شد.')
|
||
}
|
||
}
|
||
|
||
async function approveAll() {
|
||
const pending = pendingChecks.value
|
||
if (!pending.length) {
|
||
toast.error('چکی برای تأیید وجود ندارد.')
|
||
return
|
||
}
|
||
|
||
const ok = await confirm({
|
||
title: 'تأیید همه چکها',
|
||
message: `آیا از تأیید ${pending.length} چک در انتظار بررسی مطمئن هستید؟`,
|
||
tone: 'primary',
|
||
confirmLabel: 'تأیید همه',
|
||
cancelLabel: 'انصراف',
|
||
})
|
||
if (!ok) return
|
||
|
||
for (const check of pending) {
|
||
const success = await applyCheckStatus(check, 'approved')
|
||
if (!success) return
|
||
}
|
||
toast.success('همه چکهای در انتظار تأیید شدند.')
|
||
}
|
||
|
||
async function finalApproveOrder() {
|
||
if (!order.value || finalizing.value) return
|
||
|
||
const ok = await confirm({
|
||
title: 'تأیید نهایی سفارش',
|
||
message:
|
||
'با تأیید نهایی، سفارش از مرحله بررسی چک خارج میشود و وارد آمادهسازی میگردد. ادامه میدهید؟',
|
||
tone: 'primary',
|
||
confirmLabel: 'تأیید نهایی',
|
||
cancelLabel: 'انصراف',
|
||
})
|
||
if (!ok) return
|
||
|
||
finalizing.value = true
|
||
try {
|
||
const updated = await ordersApi.updateStatus(order.value.id, {
|
||
status: 'final_approved',
|
||
note: 'تأیید نهایی پس از تأیید همه چکها',
|
||
})
|
||
order.value = updated
|
||
toast.success('سفارش تأیید نهایی شد.')
|
||
} catch (err) {
|
||
toast.error(err instanceof ApiError ? err.message : 'تأیید نهایی انجام نشد.')
|
||
} finally {
|
||
finalizing.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="checks-page">
|
||
<div class="admin-page-header">
|
||
<div>
|
||
<h1 class="admin-page-header__title">مدیریت چکهای سفارش</h1>
|
||
</div>
|
||
<div v-if="displayOrder" class="checks-page__order-meta">
|
||
<StatusBadge :status="displayOrder.status" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="checks-page__stats">
|
||
<div class="checks-page__stat">
|
||
<span>تعداد</span>
|
||
<strong>{{ orderChecks.length }}</strong>
|
||
</div>
|
||
<div class="checks-page__stat">
|
||
<span>راس</span>
|
||
<strong>{{ order?.checkRassDays ?? '—' }} روز</strong>
|
||
</div>
|
||
<div class="checks-page__stat">
|
||
<span>مجموع چک</span>
|
||
<strong>{{ formatCurrency(totalCheckAmount) }}</strong>
|
||
</div>
|
||
<div class="checks-page__stat">
|
||
<span>مبلغ سفارش</span>
|
||
<strong>{{ displayOrder?.totalAmount ? formatCurrency(displayOrder.totalAmount) : '—' }}</strong>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="admin-tabs">
|
||
<button
|
||
type="button"
|
||
class="admin-tab"
|
||
:class="{ active: activeTab === 'info' }"
|
||
@click="activeTab = 'info'"
|
||
>
|
||
چکها ({{ orderChecks.length }})
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="admin-tab"
|
||
:class="{ active: activeTab === 'history' }"
|
||
@click="activeTab = 'history'"
|
||
>
|
||
تاریخچه
|
||
</button>
|
||
</div>
|
||
|
||
<template v-if="activeTab === 'info'">
|
||
<div v-if="!orderChecks.length" class="admin-empty-placeholder admin-card">
|
||
چکی برای این سفارش ثبت نشده است.
|
||
</div>
|
||
|
||
<div v-else class="checks-page__list admin-card">
|
||
<div
|
||
v-for="check in orderChecks"
|
||
:key="check.id"
|
||
class="check-row"
|
||
:class="{ open: expandedId === check.id }"
|
||
>
|
||
<button type="button" class="check-row__main" @click="toggleExpand(check)">
|
||
<span class="check-row__chevron" aria-hidden="true">{{ expandedId === check.id ? '▾' : '◂' }}</span>
|
||
|
||
<span
|
||
v-if="check.frontImage || check.backImage"
|
||
class="check-row__thumb"
|
||
@click.stop="previewImage = (check.frontImage || check.backImage)!"
|
||
>
|
||
<img :src="check.frontImage || check.backImage" alt="" />
|
||
</span>
|
||
<span v-else class="check-row__thumb check-row__thumb--empty">بدون عکس</span>
|
||
|
||
<span class="check-row__meta">
|
||
<strong>چک {{ check.row }}</strong>
|
||
<small>
|
||
{{ formatCurrency(check.amount) }}
|
||
<template v-if="check.bankName"> · {{ check.bankName }}</template>
|
||
· {{ formatDate(check.dueDate) }}
|
||
</small>
|
||
</span>
|
||
|
||
<StatusBadge :status="checkBadgeStatus(check.status)" />
|
||
</button>
|
||
|
||
<div class="check-row__actions" @click.stop>
|
||
<template v-if="check.status === 'pending_review'">
|
||
<button
|
||
type="button"
|
||
class="admin-btn admin-btn--outline-success admin-btn--xs"
|
||
:disabled="busyId === check.id"
|
||
@click="updateCheckStatus(check, 'approved')"
|
||
>
|
||
تأیید
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="admin-btn admin-btn--outline-danger admin-btn--xs"
|
||
:disabled="busyId === check.id"
|
||
@click="updateCheckStatus(check, 'rejected')"
|
||
>
|
||
رد
|
||
</button>
|
||
</template>
|
||
</div>
|
||
|
||
<div v-if="expandedId === check.id" class="check-row__panel">
|
||
<div v-if="detailFields(check).length" class="check-row__fields">
|
||
<div
|
||
v-for="field in detailFields(check)"
|
||
:key="field.label"
|
||
class="check-row__field"
|
||
>
|
||
<span>{{ field.label }}</span>
|
||
<strong :dir="field.ltr ? 'ltr' : undefined">{{ field.value }}</strong>
|
||
</div>
|
||
</div>
|
||
<p v-else class="check-row__hint">جزئیات متنی بیشتری ثبت نشده است.</p>
|
||
|
||
<div class="check-row__photos">
|
||
<button
|
||
v-if="check.frontImage"
|
||
type="button"
|
||
class="check-row__photo"
|
||
@click="previewImage = check.frontImage!"
|
||
>
|
||
<img :src="check.frontImage" alt="روی چک" />
|
||
<span>روی</span>
|
||
</button>
|
||
<button
|
||
v-if="check.backImage"
|
||
type="button"
|
||
class="check-row__photo"
|
||
@click="previewImage = check.backImage!"
|
||
>
|
||
<img :src="check.backImage" alt="پشت چک" />
|
||
<span>پشت</span>
|
||
</button>
|
||
<p v-if="!check.frontImage && !check.backImage" class="check-row__hint">
|
||
تصویری ثبت نشده.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="checks-page__footer">
|
||
<p class="checks-page__note">
|
||
<template v-if="canFinalApprove">
|
||
همه چکها تأیید شدهاند. برای ادامه، تأیید نهایی سفارش را ثبت کنید.
|
||
</template>
|
||
<template v-else-if="order?.checkRassDate">
|
||
راس: {{ formatDate(order.checkRassDate) }}
|
||
</template>
|
||
<template v-else>راس بر اساس تاریخ ثبت سفارش محاسبه میشود.</template>
|
||
</p>
|
||
<div class="admin-action-group">
|
||
<button
|
||
v-if="canFinalApprove"
|
||
type="button"
|
||
class="admin-btn admin-btn--success admin-btn--sm"
|
||
:disabled="finalizing"
|
||
@click="finalApproveOrder"
|
||
>
|
||
{{ finalizing ? 'در حال ثبت…' : 'تأیید نهایی سفارش' }}
|
||
</button>
|
||
<button
|
||
v-if="pendingChecks.length"
|
||
type="button"
|
||
class="admin-btn admin-btn--success admin-btn--sm"
|
||
@click="approveAll"
|
||
>
|
||
تأیید همه چکها
|
||
</button>
|
||
<button type="button" class="admin-btn admin-btn--outline admin-btn--sm" @click="goBack">
|
||
بازگشت
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template v-else>
|
||
<div
|
||
v-if="!reviewHistory.length && !checkReviewNotes.length"
|
||
class="admin-empty-placeholder admin-card"
|
||
>
|
||
هنوز تاریخچهای ثبت نشده است.
|
||
</div>
|
||
<div v-else class="checks-page__history">
|
||
<article
|
||
v-for="item in reviewHistory"
|
||
:key="item.id"
|
||
class="admin-card checks-page__history-item"
|
||
>
|
||
<div class="checks-page__history-item-head">
|
||
<strong>
|
||
{{ item.fromStatus ? rawStatusLabel(item.fromStatus) : '—' }}
|
||
→
|
||
{{ rawStatusLabel(item.toStatus) }}
|
||
</strong>
|
||
<span>{{ formatDateTime(item.changedAt) }}</span>
|
||
</div>
|
||
<p class="checks-page__history-meta">توسط {{ actorLabel(item.changedBy) }}</p>
|
||
<p v-if="item.note" class="checks-page__history-note">{{ item.note }}</p>
|
||
<p v-if="item.reason" class="checks-page__history-note">دلیل: {{ item.reason }}</p>
|
||
</article>
|
||
|
||
<article
|
||
v-for="note in checkReviewNotes"
|
||
:key="`note-${note.id}`"
|
||
class="admin-card checks-page__history-item"
|
||
>
|
||
<div class="checks-page__history-item-head">
|
||
<strong>یادداشت چک {{ note.row }}</strong>
|
||
<span>{{ formatDateTime(note.updatedAt) }}</span>
|
||
</div>
|
||
<StatusBadge :status="checkBadgeStatus(note.status)" />
|
||
<p class="checks-page__history-note">{{ note.notes }}</p>
|
||
</article>
|
||
</div>
|
||
</template>
|
||
|
||
<Teleport to="body">
|
||
<div
|
||
v-if="previewImage"
|
||
class="checks-page__lightbox"
|
||
@click.self="previewImage = null"
|
||
>
|
||
<button type="button" class="checks-page__lightbox-close" @click="previewImage = null">
|
||
بستن
|
||
</button>
|
||
<img :src="previewImage" alt="پیشنمایش" />
|
||
</div>
|
||
</Teleport>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.checks-page__order-meta {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.checks-page__stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 8px;
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
.checks-page__stat {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
padding: 10px 12px;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--color-surface);
|
||
}
|
||
|
||
.checks-page__stat span {
|
||
font-size: 0.75rem;
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.checks-page__stat strong {
|
||
font-size: 0.875rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.checks-page__list {
|
||
overflow: hidden;
|
||
}
|
||
|
||
.check-row {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
gap: 8px 12px;
|
||
align-items: center;
|
||
padding: 8px 12px;
|
||
border-bottom: 1px solid var(--color-border-light);
|
||
}
|
||
|
||
.check-row:last-child {
|
||
border-bottom: 0;
|
||
}
|
||
|
||
.check-row.open {
|
||
background: color-mix(in srgb, var(--color-primary-soft) 55%, transparent);
|
||
}
|
||
|
||
.check-row__main {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
min-width: 0;
|
||
border: 0;
|
||
background: transparent;
|
||
color: inherit;
|
||
text-align: start;
|
||
cursor: pointer;
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.check-row__chevron {
|
||
width: 14px;
|
||
color: var(--color-text-muted);
|
||
font-size: 0.75rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.check-row__thumb {
|
||
width: 44px;
|
||
height: 32px;
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--color-border);
|
||
flex-shrink: 0;
|
||
padding: 0;
|
||
background: #111;
|
||
display: grid;
|
||
place-items: center;
|
||
cursor: zoom-in;
|
||
}
|
||
|
||
.check-row__thumb img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
|
||
.check-row__thumb--empty {
|
||
font-size: 0.625rem;
|
||
color: var(--color-text-muted);
|
||
cursor: default;
|
||
}
|
||
|
||
.check-row__meta {
|
||
min-width: 0;
|
||
display: grid;
|
||
gap: 2px;
|
||
flex: 1;
|
||
}
|
||
|
||
.check-row__meta strong {
|
||
font-size: 0.875rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.check-row__meta small {
|
||
font-size: 0.75rem;
|
||
color: var(--color-text-muted);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.check-row__actions {
|
||
display: flex;
|
||
gap: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.check-row__panel {
|
||
grid-column: 1 / -1;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px 16px;
|
||
align-items: flex-start;
|
||
padding: 8px 0 4px 24px;
|
||
border-top: 1px dashed var(--color-border);
|
||
}
|
||
|
||
.check-row__fields {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
flex: 1;
|
||
min-width: 200px;
|
||
}
|
||
|
||
.check-row__field {
|
||
display: inline-flex;
|
||
align-items: baseline;
|
||
gap: 6px;
|
||
padding: 4px 8px;
|
||
border-radius: 999px;
|
||
background: var(--color-border-light);
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.check-row__field span {
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.check-row__field strong {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.check-row__photos {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.check-row__photo {
|
||
width: 96px;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
background: transparent;
|
||
color: inherit;
|
||
padding: 0;
|
||
cursor: zoom-in;
|
||
text-align: center;
|
||
}
|
||
|
||
.check-row__photo img {
|
||
width: 100%;
|
||
height: 60px;
|
||
object-fit: cover;
|
||
display: block;
|
||
background: #111;
|
||
}
|
||
|
||
.check-row__photo span {
|
||
display: block;
|
||
padding: 4px;
|
||
font-size: 0.6875rem;
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.check-row__hint {
|
||
margin: 0;
|
||
font-size: 0.75rem;
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.checks-page__footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-top: 14px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.checks-page__note {
|
||
margin: 0;
|
||
font-size: 0.75rem;
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.checks-page__history {
|
||
display: grid;
|
||
gap: 8px;
|
||
}
|
||
|
||
.checks-page__history-item {
|
||
padding: 12px;
|
||
display: grid;
|
||
gap: 6px;
|
||
}
|
||
|
||
.checks-page__history-item-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.checks-page__history-meta,
|
||
.checks-page__history-note {
|
||
margin: 0;
|
||
font-size: 0.8125rem;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.checks-page__history-meta {
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.checks-page__lightbox {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 300000;
|
||
display: grid;
|
||
place-items: center;
|
||
background: rgba(0, 0, 0, 0.84);
|
||
padding: 20px;
|
||
}
|
||
|
||
.checks-page__lightbox img {
|
||
max-width: min(900px, 100%);
|
||
max-height: 88vh;
|
||
object-fit: contain;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.checks-page__lightbox-close {
|
||
position: absolute;
|
||
top: 14px;
|
||
inset-inline-end: 14px;
|
||
border: 0;
|
||
border-radius: 8px;
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
@media (max-width: 800px) {
|
||
.checks-page__stats {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.check-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.check-row__actions {
|
||
padding-inline-start: 24px;
|
||
}
|
||
|
||
.check-row__panel {
|
||
padding-inline-start: 0;
|
||
}
|
||
}
|
||
</style>
|