Files
hedena_gold/apps/admin-panel/src/views/order/OrderDetailBody.vue
T

1014 lines
25 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
Shared order-detail body for retail / omde / tamin list modals.
Shows customer summary + item rows with product images. In editMode, a
product→variant picker sits beside the line list (search products, then
pick a variant from that product).
-->
<template>
<div v-if="order" class="od" :class="{ 'od--edit': editMode }">
<a-alert
v-if="order.revisionStatus === 'AWAITING_CUSTOMER'"
type="warning"
show-icon
class="od__alert"
:message="order.confirmed_str || 'در انتظار تأیید مشتری'"
description="تا تأیید مشتری (یا اعمال/لغو توسط شما) ارقام فعلی سفارش تغییر نمی‌کند. پیامک برای مشتری ارسال شده است."
/>
<section class="od__summary" aria-label="خلاصه سفارش">
<div class="od__who">
<div class="od__avatar" aria-hidden="true">
{{ initials }}
</div>
<div class="od__whoText">
<h3 class="od__name">{{ customerName }}</h3>
<a v-if="order.customer?.phone" class="od__phone" :href="`tel:${order.customer.phone}`">
{{ order.customer.phone }}
</a>
</div>
</div>
<div class="od__meta">
<div class="od__metaItem">
<span class="od__metaLabel">تاریخ ثبت</span>
<span class="od__metaValue">{{ formatDate(order.caj) }}</span>
</div>
<div class="od__metaItem">
<span class="od__metaLabel">وضعیت</span>
<a-tag color="blue">{{ order.sts_str }}</a-tag>
</div>
<div v-if="order.orderNumber" class="od__metaItem">
<span class="od__metaLabel">شماره سفارش</span>
<span class="od__metaValue od__mono">{{ order.orderNumber }}</span>
</div>
</div>
</section>
<div class="od__workspace">
<section class="od__items" aria-label="آیتم‌های سفارش">
<header class="od__itemsHead">
<h4 class="od__itemsTitle">آیتم‌های سفارش</h4>
<span class="od__itemsCount">{{ order.items?.length || 0 }} قلم</span>
</header>
<ul v-if="order.items?.length" class="od__list">
<li v-for="(item, index) in order.items" :key="item.id || index" class="od__row">
<div class="od__art">
<img
v-if="item.image && !broken[itemKey(item, index)]"
:src="item.image"
:alt="item.title || ''"
@error="markBroken(itemKey(item, index))"
/>
<div v-else class="od__artEmpty" aria-hidden="true">
<PictureOutlined />
</div>
</div>
<div class="od__body">
<div class="od__titles">
<h5 class="od__product">{{ item.title || 'محصول' }}</h5>
<span v-if="item.variant_title" class="od__variant">{{ item.variant_title }}</span>
</div>
<div v-if="!editMode" class="od__chips">
<span class="od__chip">
<b>{{ item.qty }}</b>
عدد
</span>
<span class="od__chip">
<b>{{ formatGrams(item.grams) }}</b>
گرم
</span>
<span v-if="item.wagePercent != null" class="od__chip">
<b>{{ formatWage(item.wagePercent) }}</b>
٪ اجرت
</span>
<span v-if="Number(item.flatRialCharge) > 0" class="od__chip od__chip--accent">
<b>{{ priceUnit(item.flatRialCharge) }}</b>
</span>
</div>
<div v-else class="od__edit">
<a-form-item label="وزن (گرم)" class="od__field">
<a-input-number v-model:value="item.grams" :min="0" class="w-full" />
</a-form-item>
<a-form-item label="تعداد" class="od__field">
<a-input-number v-model:value="item.qty" :min="1" class="w-full" />
</a-form-item>
<a-form-item label="اجرت ریالی" class="od__field">
<a-input-number v-model:value="item.flatRialCharge" :min="0" class="w-full" />
</a-form-item>
<a-button size="small" danger class="od__remove" @click="emit('remove-line', index)">
حذف آیتم
</a-button>
</div>
</div>
</li>
</ul>
<a-empty v-else description="آیتمی در این سفارش نیست" />
</section>
<aside v-if="editMode" class="od__picker" aria-label="افزودن محصول">
<header class="od__pickerHead">
<div>
<h4 class="od__pickerTitle">افزودن به سفارش</h4>
<p class="od__pickerHint">اول محصول را پیدا کنید، بعد تنوع را انتخاب کنید</p>
</div>
<ol class="od__steps" aria-hidden="true">
<li :class="{ 'is-active': !selectedProduct, 'is-done': !!selectedProduct }">۱ محصول</li>
<li :class="{ 'is-active': !!selectedProduct }">۲ تنوع</li>
</ol>
</header>
<a-form-item label="جستجوی محصول" class="od__field">
<a-input-search
v-model:value="productSearch"
allow-clear
placeholder="نام یا کد محصول…"
:loading="productsLoading"
enter-button="جستجو"
@search="onProductSearchSubmit"
/>
</a-form-item>
<div
v-if="categories.length"
class="od__catChips"
role="tablist"
aria-label="دسته‌بندی محصولات"
>
<button
type="button"
class="od__catChip"
:class="{ 'is-active': !selectedCategoryId }"
role="tab"
:aria-selected="!selectedCategoryId"
@click="selectCategory(null)"
>
همه
</button>
<button
v-for="cat in categories"
:key="cat.id"
type="button"
class="od__catChip"
:class="{ 'is-active': selectedCategoryId === cat.id }"
role="tab"
:aria-selected="selectedCategoryId === cat.id"
@click="selectCategory(cat.id)"
>
{{ cat.title }}
</button>
</div>
<a-spin :spinning="productsLoading">
<div v-if="products.length" class="od__products" role="listbox" aria-label="محصولات">
<button
v-for="p in products"
:key="p.id"
type="button"
class="od__productCard"
:class="{ 'is-selected': selectedProduct?.id === p.id }"
role="option"
:aria-selected="selectedProduct?.id === p.id"
@click="selectProduct(p)"
>
<div class="od__productThumb">
<img
v-if="productThumb(p) && !broken[`p-${p.id}`]"
:src="productThumb(p)!"
:alt="p.title"
@error="markBroken(`p-${p.id}`)"
/>
<div v-else class="od__artEmpty" aria-hidden="true">
<PictureOutlined />
</div>
</div>
<div class="od__productMeta">
<span class="od__productName">{{ p.title }}</span>
<span class="od__productSub">
{{ p.variants_count ?? p.variants_all?.length ?? 0 }} تنوع
<template v-if="p.product_code"> · {{ p.product_code }}</template>
</span>
</div>
</button>
</div>
<a-empty
v-else-if="!productsLoading"
class="od__pickerEmpty"
:description="productsEmptyHint"
/>
</a-spin>
<div v-if="selectedProduct" class="od__variantsBlock">
<div class="od__variantsHead">
<h5 class="od__variantsTitle">تنوع‌های «{{ selectedProduct.title }}»</h5>
<a-button type="link" size="small" @click="clearProduct">عوض کردن محصول</a-button>
</div>
<div v-if="activeVariants.length" class="od__variants" role="listbox" aria-label="تنوع‌ها">
<button
v-for="v in activeVariants"
:key="v.id"
type="button"
class="od__variantCard"
:class="{ 'is-selected': localVariantId === String(v.id) }"
role="option"
:aria-selected="localVariantId === String(v.id)"
@click="localVariantId = String(v.id)"
>
<div class="od__variantTop">
<span class="od__variantName">{{ v.title || 'تنوع' }}</span>
<a-tag v-if="localVariantId === String(v.id)" color="blue">انتخاب‌شده</a-tag>
</div>
<div class="od__variantFacts">
<span><b>{{ formatGrams(v.grams) }}</b> گرم</span>
<span v-if="v.percentage_pay != null"><b>{{ formatWage(v.percentage_pay) }}</b>٪ اجرت</span>
<span v-if="v.sku" class="od__mono">{{ v.sku }}</span>
</div>
</button>
</div>
<a-empty v-else description="این محصول تنوع فعالی ندارد" />
<div class="od__addBar">
<a-form-item label="تعداد" class="od__field od__addQty">
<a-input-number v-model:value="localQty" :min="1" class="w-full" />
</a-form-item>
<a-button type="primary" class="od__addBtn" :disabled="!localVariantId" @click="onAdd">
افزودن به سفارش
</a-button>
</div>
</div>
</aside>
</div>
</div>
<a-empty v-else />
</template>
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import { PictureOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import api from '../../api/client';
import { jdate, priceUnit } from '../../utils/filters';
export type AddLinePayload = {
variantId: string;
qty: number;
title?: string;
variantTitle?: string;
grams?: number;
wagePercent?: number;
flatRialCharge?: number;
image?: string | null;
};
const props = defineProps<{
order: Record<string, any> | undefined;
editMode?: boolean;
addVariantId?: string;
addQty?: number;
}>();
const emit = defineEmits<{
(e: 'remove-line', index: number): void;
(e: 'add-line', payload: AddLinePayload): void;
(e: 'update:addVariantId', value: string): void;
(e: 'update:addQty', value: number): void;
}>();
const localVariantId = ref(props.addVariantId ?? '');
const localQty = ref(props.addQty ?? 1);
const productsLoading = ref(false);
const products = ref<Record<string, any>[]>([]);
const productSearch = ref('');
const selectedProduct = ref<Record<string, any> | null>(null);
const categories = ref<{ id: string; title: string }[]>([]);
const selectedCategoryId = ref<string | null>(null);
let searchTimer: ReturnType<typeof setTimeout> | null = null;
/** Broken/missing files (empty local uploads dir, SPA HTML fallback, …). */
const broken = reactive<Record<string, boolean>>({});
const activeVariants = computed(() => {
const all = selectedProduct.value?.variants_all ?? [];
return all.filter((v: any) => v.is_active !== false);
});
const productsEmptyHint = computed(() => {
if (productSearch.value.trim() || selectedCategoryId.value) return 'محصولی پیدا نشد';
return 'برای شروع، محصول را جستجو کنید یا یک دسته انتخاب کنید';
});
function formatGrams(v: unknown) {
const n = parseFloat(String(v));
if (!Number.isFinite(n)) return String(v ?? '—');
return n % 1 === 0 ? String(n) : n.toFixed(3);
}
function productThumb(p: Record<string, any>): string | null {
return p.thumb?.full || p.images?.[0]?.url || null;
}
async function loadCategories() {
try {
const { data } = await api.get('product/categories');
const rows = Array.isArray(data) ? data : Array.isArray(data?.rows) ? data.rows : [];
categories.value = rows
.map((c: any) => ({ id: String(c.id), title: String(c.title ?? '').trim() }))
.filter((c: { id: string; title: string }) => c.id && c.title);
} catch (e) {
console.log(e);
categories.value = [];
}
}
async function loadProducts(search = productSearch.value) {
productsLoading.value = true;
try {
const filters: { field: string; op: string; value: unknown }[] = [
{ field: 'isActive', op: '=', value: true },
];
if (selectedCategoryId.value) {
filters.push({ field: 'categoryId', op: '=', value: selectedCategoryId.value });
}
const { data } = await api.post('products', {
page: 1,
limit: 24,
search: String(search ?? '').trim() || undefined,
filters,
});
products.value = Array.isArray(data?.rows) ? data.rows : [];
} catch (e) {
console.log(e);
message.error('دریافت لیست محصولات ممکن نشد');
} finally {
productsLoading.value = false;
}
}
function onProductSearchSubmit(q: string) {
productSearch.value = q;
void loadProducts(q);
}
function selectCategory(id: string | null) {
if (selectedCategoryId.value === id) return;
selectedCategoryId.value = id;
clearProduct();
void loadProducts();
}
function selectProduct(p: Record<string, any>) {
selectedProduct.value = p;
localVariantId.value = '';
const first = (p.variants_all ?? []).find((v: any) => v.is_active !== false);
if (first) localVariantId.value = String(first.id);
}
function clearProduct() {
selectedProduct.value = null;
localVariantId.value = '';
}
function itemKey(item: Record<string, any>, index: number) {
return String(item.id || item.productVariantId || index);
}
function markBroken(key: string) {
broken[key] = true;
}
watch(
() => props.order?.id,
() => {
for (const k of Object.keys(broken)) delete broken[k];
},
);
watch(
() => props.addVariantId,
(v) => {
if (v !== undefined) localVariantId.value = v;
},
);
watch(
() => props.addQty,
(v) => {
if (v !== undefined) localQty.value = v;
},
);
watch(localVariantId, (v) => emit('update:addVariantId', v));
watch(localQty, (v) => emit('update:addQty', Number(v) || 1));
watch(productSearch, (q) => {
if (searchTimer) clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
void loadProducts(q);
}, 320);
});
watch(
() => props.editMode,
(on) => {
if (on) {
void loadCategories();
void loadProducts('');
} else {
clearProduct();
products.value = [];
productSearch.value = '';
selectedCategoryId.value = null;
categories.value = [];
}
},
);
const customerName = computed(() => {
const c = props.order?.customer;
if (!c) return '—';
const name = `${c.fname ?? ''} ${c.lname ?? ''}`.trim();
return name || c.fullName || '—';
});
const initials = computed(() => {
const parts = customerName.value.split(/\s+/).filter(Boolean);
if (!parts.length || parts[0] === '—') return '؟';
return parts
.slice(0, 2)
.map((p: string) => p[0])
.join('');
});
function formatDate(v: string | null | undefined) {
return v ? jdate(v) : '—';
}
function formatWage(v: unknown) {
const n = parseFloat(String(v));
if (!Number.isFinite(n)) return String(v ?? '—');
return n % 1 === 0 ? String(n) : n.toFixed(2);
}
function onAdd() {
const variantId = String(localVariantId.value ?? '').trim();
if (!variantId) {
message.warning('یک تنوع انتخاب کنید');
return;
}
if (!selectedProduct.value) {
message.warning('اول یک محصول انتخاب کنید');
return;
}
const row = activeVariants.value.find((v: any) => String(v.id) === variantId);
const thumb = row?.image?.url || productThumb(selectedProduct.value);
emit('add-line', {
variantId,
qty: Number(localQty.value) || 1,
title: selectedProduct.value.title ?? 'محصول',
variantTitle: row?.title ?? variantId,
grams: row?.grams != null ? Number(row.grams) : 0,
wagePercent: row?.percentage_pay != null ? Number(row.percentage_pay) : undefined,
flatRialCharge: row?.rial_pay != null ? Number(row.rial_pay) : 0,
image: thumb ?? null,
});
message.success('آیتم به سفارش اضافه شد');
}
</script>
<style scoped>
.od__alert {
margin-bottom: 16px;
}
.od__summary {
display: flex;
flex-wrap: wrap;
gap: 16px 24px;
align-items: flex-start;
justify-content: space-between;
padding: 14px 16px;
border: 1px solid #f0f0f0;
border-radius: 12px;
background: #fafafa;
margin-bottom: 18px;
}
.od__who {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.od__avatar {
width: 44px;
height: 44px;
border-radius: 50%;
display: grid;
place-items: center;
flex: none;
font-size: 14px;
font-weight: 700;
color: #1d39c4;
background: #f0f5ff;
border: 1px solid #d6e4ff;
}
.od__whoText {
min-width: 0;
}
.od__name {
margin: 0;
font-size: 15px;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
line-height: 1.4;
}
.od__phone {
display: inline-block;
margin-top: 2px;
font-size: 13px;
color: rgba(0, 0, 0, 0.55);
direction: ltr;
unicode-bidi: plaintext;
}
.od__meta {
display: flex;
flex-wrap: wrap;
gap: 12px 20px;
}
.od__metaItem {
display: flex;
flex-direction: column;
gap: 4px;
}
.od__metaLabel {
font-size: 11px;
font-weight: 600;
color: rgba(0, 0, 0, 0.45);
}
.od__metaValue {
font-size: 13px;
font-weight: 600;
color: rgba(0, 0, 0, 0.88);
}
.od__mono {
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
}
.od__workspace {
display: block;
}
.od--edit .od__workspace {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
gap: 16px;
align-items: start;
}
.od__itemsHead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.od__itemsTitle {
margin: 0;
font-size: 14px;
font-weight: 700;
}
.od__itemsCount {
font-size: 12px;
font-weight: 600;
color: rgba(0, 0, 0, 0.45);
}
.od__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
max-height: min(58vh, 560px);
overflow: auto;
padding-inline-end: 4px;
}
.od__row {
display: flex;
gap: 12px;
padding: 12px;
border: 1px solid #f0f0f0;
border-radius: 12px;
background: #fff;
}
.od__art {
width: 72px;
height: 72px;
flex: none;
border-radius: 10px;
overflow: hidden;
background: #f5f5f5;
border: 1px solid #f0f0f0;
}
.od__art img,
.od__productThumb img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.od__artEmpty {
width: 100%;
height: 100%;
display: grid;
place-items: center;
color: rgba(0, 0, 0, 0.25);
font-size: 22px;
}
.od__body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.od__titles {
min-width: 0;
}
.od__product {
margin: 0;
font-size: 13.5px;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
line-height: 1.45;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.od__variant {
display: block;
margin-top: 2px;
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
}
.od__chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.od__chip {
display: inline-flex;
align-items: baseline;
gap: 4px;
padding: 3px 9px;
border-radius: 999px;
font-size: 11px;
color: rgba(0, 0, 0, 0.55);
background: #f5f5f5;
border: 1px solid #f0f0f0;
}
.od__chip b {
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
font-variant-numeric: tabular-nums;
}
.od__chip--accent {
color: #531dab;
background: #f9f0ff;
border-color: #efdbff;
}
.od__chip--accent b {
color: #531dab;
}
.od__edit {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
gap: 8px 12px;
align-items: end;
}
.od__field {
margin-bottom: 0;
}
.od__remove {
justify-self: start;
}
/* ── Picker panel ───────────────────────────────────────── */
.od__picker {
position: sticky;
top: 0;
border: 1px solid #e6ebf5;
border-radius: 14px;
background: linear-gradient(180deg, #f7f9fc 0%, #fff 48px);
padding: 14px;
box-shadow: 0 8px 24px -18px rgba(20, 30, 60, 0.35);
max-height: min(70vh, 680px);
overflow: auto;
}
.od__pickerHead {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 12px;
}
.od__pickerTitle {
margin: 0;
font-size: 14px;
font-weight: 800;
color: rgba(0, 0, 0, 0.88);
}
.od__pickerHint {
margin: 4px 0 0;
font-size: 12px;
line-height: 1.6;
color: rgba(0, 0, 0, 0.45);
}
.od__steps {
list-style: none;
margin: 0;
padding: 0;
display: flex;
gap: 8px;
}
.od__steps li {
flex: 1;
text-align: center;
padding: 6px 8px;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
color: rgba(0, 0, 0, 0.35);
background: #eef1f6;
}
.od__steps li.is-active {
color: #1d39c4;
background: #e6f0ff;
}
.od__steps li.is-done {
color: #389e0d;
background: #f6ffed;
}
.od__catChips {
display: flex;
gap: 6px;
margin: 10px 0 2px;
padding-bottom: 2px;
overflow-x: auto;
scrollbar-width: thin;
}
.od__catChip {
flex: none;
padding: 5px 12px;
border-radius: 999px;
border: 1px solid #e6ebf5;
background: #fff;
font-size: 12px;
font-weight: 600;
color: rgba(0, 0, 0, 0.65);
cursor: pointer;
white-space: nowrap;
transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.od__catChip:hover {
border-color: #91caff;
color: #1d39c4;
}
.od__catChip.is-active {
border-color: #1677ff;
background: #e6f4ff;
color: #0958d9;
}
.od__products {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 10px;
max-height: 220px;
overflow: auto;
}
.od__productCard {
display: flex;
gap: 10px;
align-items: center;
width: 100%;
padding: 8px;
border-radius: 12px;
border: 1px solid #eef0f4;
background: #fff;
cursor: pointer;
text-align: right;
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.od__productCard:hover {
border-color: #91caff;
background: #fafcff;
}
.od__productCard.is-selected {
border-color: #1677ff;
background: #f0f5ff;
box-shadow: 0 0 0 1px rgba(22, 119, 255, 0.2);
}
.od__productThumb {
width: 48px;
height: 48px;
flex: none;
border-radius: 10px;
overflow: hidden;
background: #f5f5f5;
border: 1px solid #f0f0f0;
}
.od__productMeta {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.od__productName {
font-size: 12.5px;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.od__productSub {
font-size: 11px;
color: rgba(0, 0, 0, 0.45);
}
.od__pickerEmpty {
margin: 12px 0 4px;
}
.od__variantsBlock {
margin-top: 14px;
padding-top: 12px;
border-top: 1px dashed #e6ebf5;
}
.od__variantsHead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
}
.od__variantsTitle {
margin: 0;
font-size: 12.5px;
font-weight: 800;
color: rgba(0, 0, 0, 0.75);
}
.od__variants {
display: flex;
flex-direction: column;
gap: 8px;
}
.od__variantCard {
width: 100%;
padding: 10px 12px;
border-radius: 12px;
border: 1px solid #eef0f4;
background: #fff;
cursor: pointer;
text-align: right;
transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.od__variantCard:hover {
border-color: #91caff;
}
.od__variantCard.is-selected {
border-color: #1677ff;
background: #f0f5ff;
box-shadow: 0 0 0 1px rgba(22, 119, 255, 0.18);
}
.od__variantTop {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 6px;
}
.od__variantName {
font-size: 13px;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
}
.od__variantFacts {
display: flex;
flex-wrap: wrap;
gap: 8px 12px;
font-size: 11.5px;
color: rgba(0, 0, 0, 0.5);
}
.od__variantFacts b {
font-weight: 700;
color: rgba(0, 0, 0, 0.78);
font-variant-numeric: tabular-nums;
}
.od__addBar {
display: grid;
grid-template-columns: 100px 1fr;
gap: 10px;
align-items: end;
margin-top: 12px;
}
.od__addBtn {
height: 36px;
}
@media (max-width: 900px) {
.od--edit .od__workspace {
grid-template-columns: 1fr;
}
.od__picker {
position: static;
max-height: none;
}
.od__list {
max-height: none;
}
}
@media (max-width: 575px) {
.od__summary {
padding: 12px;
}
.od__art {
width: 56px;
height: 56px;
}
.od__row {
padding: 10px;
}
.od__addBar {
grid-template-columns: 1fr;
}
}
</style>