changes
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import InventoryHighlightCard from '@/components/inventory/InventoryHighlightCard.vue';
|
||||
import InventoryTable from '@/components/inventory/InventoryTable.vue';
|
||||
|
||||
const inventoryHighlights = [
|
||||
{
|
||||
@@ -31,13 +32,16 @@ const inventoryHighlights = [
|
||||
];
|
||||
</script>
|
||||
<template>
|
||||
<div class="fixed-grid has-2-cols-mobile has-4-cols">
|
||||
<div class="grid">
|
||||
<InventoryHighlightCard
|
||||
v-for="highlight in inventoryHighlights"
|
||||
:key="highlight.name"
|
||||
v-bind="highlight"
|
||||
/>
|
||||
<div>
|
||||
<div class="fixed-grid has-2-cols-mobile has-4-cols">
|
||||
<div class="grid">
|
||||
<InventoryHighlightCard
|
||||
v-for="highlight in inventoryHighlights"
|
||||
:key="highlight.name"
|
||||
v-bind="highlight"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<InventoryTable />
|
||||
</div>
|
||||
</template>
|
||||
47
app/components/inventory/InventoryRow.vue
Normal file
47
app/components/inventory/InventoryRow.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
invItem: Object,
|
||||
selected: Boolean,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['selectInvItem', 'unselect']);
|
||||
const { $dayjs } = useNuxtApp();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr
|
||||
:class="['is-clickable', selected && 'is-selected']"
|
||||
@click="selected ? emit('unselect') : emit('selectInvItem', invItem.id)"
|
||||
>
|
||||
<td>
|
||||
<div>
|
||||
<p class="fs-14">{{ invItem.name }}</p>
|
||||
<p class="fs-12 has-text-grey">{{ invItem.category }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="is-family-monospace fs-12">{{ invItem.sku }}</td>
|
||||
<td>{{ invItem.storage }}</td>
|
||||
<td class="is-family-monospace fs-12">{{ invItem.storage__position }}</td>
|
||||
<td class="has-text-right">{{ invItem.stock }}</td>
|
||||
<td class="has-text-right">{{ invItem.preorder }}</td>
|
||||
<td
|
||||
:class="['has-text-right', invItem.status === 'OK' ? 'has-text-success-30' : 'has-text-warning-30']"
|
||||
>{{ invItem.available }}</td>
|
||||
<td class="is-family-monospace fs-12">{{ invItem.batch }}</td>
|
||||
<td>{{ $dayjs(invItem.expired).format('L') }}</td>
|
||||
<td>
|
||||
<span
|
||||
:class="['tag is-rounded', invItem.status === 'OK' ? 'has-background-success-soft has-text-success-bold' : 'has-background-warning-soft has-text-warning-bold']"
|
||||
>{{ invItem.status }}</span></td>
|
||||
</tr>
|
||||
</template>
|
||||
<style scoped>
|
||||
tr.is-selected {
|
||||
--bulma-table-row-active-background-color: var(--bulma-primary-95);
|
||||
color: unset;
|
||||
}
|
||||
td {
|
||||
vertical-align: middle;
|
||||
--bulma-table-cell-padding: 0.75em;
|
||||
}
|
||||
</style>
|
||||
566
app/components/inventory/InventoryTable.vue
Normal file
566
app/components/inventory/InventoryTable.vue
Normal file
@@ -0,0 +1,566 @@
|
||||
<script setup>
|
||||
import InventoryRow from '@/components/inventory/InventoryRow.vue';
|
||||
import SelectedInvItem from '@/components/inventory/SelectedInvItem.vue';
|
||||
|
||||
const invItems = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Dầu gội thảo mộc 500ml',
|
||||
category: 'Chăm sóc tóc',
|
||||
sku: 'DG-012',
|
||||
storage: 'Kho TP.HCM',
|
||||
storage__position: 'WH-HCM/C-01',
|
||||
stock: 200,
|
||||
preorder: 50,
|
||||
available: 150,
|
||||
unit_price: '120000.00',
|
||||
total: '24000000.00',
|
||||
batch: 'BATCH-2024-012',
|
||||
expired: '2026-08-20',
|
||||
status: 'OK',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 1,
|
||||
type: 1,
|
||||
type__name: 'Nhập',
|
||||
date: '2026-04-03',
|
||||
code: 'PO-009',
|
||||
to__code: 'WH-HCM/C-01',
|
||||
delta: 300,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-04-04',
|
||||
code: 'SO-034',
|
||||
from__code: 'WH-HCM/C-01',
|
||||
delta: -100
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Gel rửa mặt làm sạch sâu',
|
||||
category: 'Mỹ phẩm',
|
||||
sku: 'GRM-006',
|
||||
storage: 'Kho Hà Nội',
|
||||
storage__position: 'WH-HN/A-03',
|
||||
stock: 12,
|
||||
preorder: 5,
|
||||
available: 7,
|
||||
unit_price: '140000.00',
|
||||
total: '1700000.00',
|
||||
batch: 'BATCH-2024-006',
|
||||
expired: '2025-11-20',
|
||||
status: 'Thấp',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 3,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-03-28',
|
||||
code: 'SO-028',
|
||||
to__code: 'WH-HN/A-03',
|
||||
delta: -50,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Kem chống nắng SPF 50+',
|
||||
category: 'Mỹ phẩm',
|
||||
sku: 'KCN-001',
|
||||
storage: 'Kho Hà Nội',
|
||||
storage__position: 'WH-HN/A-01',
|
||||
stock: 150,
|
||||
preorder: 20,
|
||||
available: 130,
|
||||
unit_price: '280000.00',
|
||||
total: '42000000.00',
|
||||
batch: 'BATCH-2024-001',
|
||||
expired: '2025-10-15',
|
||||
status: 'OK',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 4,
|
||||
type: 1,
|
||||
type__name: 'Nhập',
|
||||
date: '2026-04-05',
|
||||
code: 'PO-001',
|
||||
from__code: 'WH-HN/A-01',
|
||||
delta: 200,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-04-06',
|
||||
code: 'SO-023',
|
||||
from__code: 'WH-HN/A-01',
|
||||
delta: -50,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Kem dưỡng ẩm ban đêm',
|
||||
category: 'Dưỡng da',
|
||||
sku: 'KD-007',
|
||||
storage: 'Kho Đà Nẵng',
|
||||
storage__position: 'WH-DN/A-01',
|
||||
stock: 8,
|
||||
preorder: 3,
|
||||
available: 5,
|
||||
unit_price: '380000.00',
|
||||
total: '30040000.00',
|
||||
batch: 'BATCH-2024-007',
|
||||
expired: '2025-09-10',
|
||||
status: 'Thấp',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 6,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-03-25',
|
||||
code: 'SO-029',
|
||||
from__code: 'WH-DN/A-01',
|
||||
delta: -40,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Kem mắt chống lão hóa',
|
||||
category: 'Dưỡng da',
|
||||
sku: 'KM-011',
|
||||
storage: 'Kho Hà Nội',
|
||||
storage__position: 'WH-HN/B-02',
|
||||
stock: 30,
|
||||
preorder: 28,
|
||||
available: 2,
|
||||
unit_price: '550000.00',
|
||||
total: '16500000.00',
|
||||
batch: 'BATCH-2024-011',
|
||||
expired: '2025-10-05',
|
||||
status: 'Thấp',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 7,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-03-27',
|
||||
code: 'SO-033',
|
||||
from__code: 'WH-HN/B-02',
|
||||
delta: -25,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Mặt nạ collagen 10 miếng',
|
||||
category: 'Mặt nạ',
|
||||
sku: 'MN-004',
|
||||
storage: 'Kho TP.HCM',
|
||||
storage__position: 'WH-HCM/A-01',
|
||||
stock: 45,
|
||||
preorder: 15,
|
||||
available: 30,
|
||||
unit_price: '320000.00',
|
||||
total: '14400000.00',
|
||||
batch: 'BATCH-2024-004',
|
||||
expired: '2025-08-15',
|
||||
status: 'OK',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 8,
|
||||
type: 1,
|
||||
type__name: 'Nhập',
|
||||
date: '2026-03-30',
|
||||
code: 'PO-004',
|
||||
to__code: 'WH-HCM/A-01',
|
||||
delta: 60,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-04-01',
|
||||
code: 'SO-026',
|
||||
from__code: 'WH-HCM/A-01',
|
||||
delta: -15,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'Nước tẩy trang 3 trong 1',
|
||||
category: 'Tẩy trang',
|
||||
sku: 'NTT-010',
|
||||
storage: 'Kho Đà Nẵng',
|
||||
storage__position: 'WH-DN/A-02',
|
||||
stock: 140,
|
||||
preorder: 35,
|
||||
available: 105,
|
||||
unit_price: '195000.00',
|
||||
total: '27300000.00',
|
||||
batch: 'BATCH-2024-010',
|
||||
expired: '2026-04-30',
|
||||
status: 'OK',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 10,
|
||||
type: 1,
|
||||
type__name: 'Nhập',
|
||||
date: '2026-03-29',
|
||||
code: 'PO-008',
|
||||
to__code: 'WH-DN/A-02',
|
||||
delta: 200,
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-04-01',
|
||||
code: 'SO-032',
|
||||
from__code: 'WH-DN/A-02',
|
||||
delta: -60,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'Phấn nền BB cream',
|
||||
category: 'Trang điểm',
|
||||
sku: 'PN-009',
|
||||
storage: 'Kho TP.HCM',
|
||||
storage__position: 'WH-HCM/B-01',
|
||||
stock: 65,
|
||||
preorder: 20,
|
||||
available: 45,
|
||||
unit_price: '220000.00',
|
||||
total: '14300000.00',
|
||||
batch: 'BATCH-2024-009',
|
||||
expired: '2025-07-15',
|
||||
status: 'OK',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 12,
|
||||
type: 1,
|
||||
type__name: 'Nhập',
|
||||
date: '2026-04-01',
|
||||
code: 'PO-007',
|
||||
to__code: 'WH-HCM/B-01',
|
||||
delta: 100,
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-04-02',
|
||||
code: 'SO-031',
|
||||
from__code: 'WH-HCM/B-01',
|
||||
delta: -35,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'Serum Vitamin C 30ml',
|
||||
category: 'Dưỡng da',
|
||||
sku: 'SER-003',
|
||||
storage: 'Kho Hà Nội',
|
||||
storage__position: 'WH-HN/B-01',
|
||||
stock: 220,
|
||||
preorder: 40,
|
||||
available: 180,
|
||||
unit_price: '450000.00',
|
||||
total: '99000000.00',
|
||||
batch: 'BATCH-2024-003',
|
||||
expired: '2025-12-31',
|
||||
status: 'OK',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 14,
|
||||
type: 1,
|
||||
type__name: 'Nhập',
|
||||
date: '2026-04-01',
|
||||
code: 'PO-003',
|
||||
to__code: 'WH-HN/B-01',
|
||||
delta: 300,
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-04-02',
|
||||
code: 'SO-025',
|
||||
from__code: 'WH-HN/B-01',
|
||||
delta: -80,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Son dưỡng môi vitamin E',
|
||||
category: 'Trang điểm',
|
||||
sku: 'SD-008',
|
||||
storage: 'Kho Hà Nội',
|
||||
storage__position: 'WH-HN/C-01',
|
||||
stock: 95,
|
||||
preorder: 45,
|
||||
available: 50,
|
||||
unit_price: '75000.00',
|
||||
total: '7100000.00',
|
||||
batch: 'BATCH-2024-008',
|
||||
expired: '2026-02-28',
|
||||
status: 'OK',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 16,
|
||||
type: 1,
|
||||
type__name: 'Nhập',
|
||||
date: '2026-04-04',
|
||||
code: 'PO-006',
|
||||
to__code: 'WH-HN/C-01',
|
||||
delta: 150,
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-04-02',
|
||||
code: 'SO-030',
|
||||
from__code: 'WH-HN/C-01',
|
||||
delta: -55,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: 'Sữa rửa mặt trà xanh 150ml',
|
||||
category: 'Mỹ phẩm',
|
||||
sku: 'SRM-002',
|
||||
storage: 'Kho Hà Nội',
|
||||
storage__position: 'WH-HN/A-02',
|
||||
stock: 85,
|
||||
preorder: 30,
|
||||
available: 55,
|
||||
unit_price: '150000.00',
|
||||
total: '12800000.00',
|
||||
batch: 'BATCH-2024-002',
|
||||
expired: '2026-03-20',
|
||||
status: 'OK',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 18,
|
||||
type: 1,
|
||||
type__name: 'Nhập',
|
||||
date: '2026-04-03',
|
||||
code: 'PO-002',
|
||||
to__code: 'WH-HN/A-02',
|
||||
delta: 100,
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-04-04',
|
||||
code: 'SO-025',
|
||||
from__code: 'WH-HN/A-02',
|
||||
delta: -15,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: 'Toner cân bằng da 200ml',
|
||||
category: 'Dưỡng da',
|
||||
sku: 'TN-005',
|
||||
storage: 'Kho TP.HCM',
|
||||
storage__position: 'WH-HCM/A-02',
|
||||
stock: 180,
|
||||
preorder: 25,
|
||||
available: 155,
|
||||
unit_price: '180000.00',
|
||||
total: '32400000.00',
|
||||
batch: 'BATCH-2024-005',
|
||||
expired: '2026-06-30',
|
||||
status: 'OK',
|
||||
moveHistory: [
|
||||
{
|
||||
id: 20,
|
||||
type: 1,
|
||||
type__name: 'Nhập',
|
||||
date: '2026-04-02',
|
||||
code: 'PO-005',
|
||||
to__code: 'WH-HCM/A-02',
|
||||
delta: 200,
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
type: 2,
|
||||
type__name: 'Xuất',
|
||||
date: '2026-04-04',
|
||||
code: 'SO-025',
|
||||
from__code: 'WH-HCM/A-02',
|
||||
delta: -15,
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
const storages = [
|
||||
'Kho Hà Nội',
|
||||
'Kho TP.HCM',
|
||||
'Kho Đà Nẵng',
|
||||
];
|
||||
|
||||
const categories = [
|
||||
'Chăm sóc tóc',
|
||||
'Dưỡng da',
|
||||
'Mỹ phẩm',
|
||||
'Mặt nạ',
|
||||
'Trang điểm',
|
||||
'Tẩy trang',
|
||||
];
|
||||
|
||||
const statuses = [
|
||||
'OK',
|
||||
'Thấp',
|
||||
];
|
||||
|
||||
const input = ref();
|
||||
const selectedStorage = ref();
|
||||
const selectedCategory = ref();
|
||||
const selectedStatus = ref();
|
||||
|
||||
const filteredInvItems = computed(() => {
|
||||
const filteredByInput = invItems.filter(invItem => {
|
||||
if (!input.value) return true;
|
||||
const values = Object.values(invItem);
|
||||
const strValues = values.filter(v => typeof v === 'string');
|
||||
return strValues.some(str => str.toLowerCase().includes(input.value.toLowerCase()));
|
||||
});
|
||||
|
||||
const filteredByStorage = filteredByInput.filter(invItem => {
|
||||
if (!selectedStorage.value) return true;
|
||||
return invItem.storage === selectedStorage.value;
|
||||
});
|
||||
|
||||
const filteredByCategory = filteredByStorage.filter(invItem => {
|
||||
if (!selectedCategory.value) return true;
|
||||
return invItem.category === selectedCategory.value;
|
||||
});
|
||||
|
||||
const filteredByStatus = filteredByCategory.filter(invItem => {
|
||||
if (!selectedStatus.value) return true;
|
||||
return invItem.status === selectedStatus.value;
|
||||
});
|
||||
|
||||
return filteredByStatus;
|
||||
})
|
||||
|
||||
const selectedInvItem = ref(null);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="is-flex is-gap-2 is-align-items-center">
|
||||
<div class="field is-flex-grow-1 mb-0">
|
||||
<p class="control has-icons-left">
|
||||
<input v-model="input" class="input" type="text" placeholder="Tìm kiếm theo tên, SKU, barcode..." />
|
||||
<span class="icon is-small is-left">
|
||||
<Icon name="material-symbols:search-rounded" :size="20" />
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="select">
|
||||
<select v-model="selectedStorage">
|
||||
<option :value="undefined">Tất cả kho</option>
|
||||
<option
|
||||
v-for="storage in storages"
|
||||
:key="storage"
|
||||
:value="storage"
|
||||
>
|
||||
{{ storage }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select">
|
||||
<select v-model="selectedCategory">
|
||||
<option :value="undefined">Tất cả danh mục</option>
|
||||
<option
|
||||
v-for="category in categories"
|
||||
:key="category"
|
||||
:value="category"
|
||||
>
|
||||
{{ category }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select">
|
||||
<select v-model="selectedStatus">
|
||||
<option :value="undefined">Trạng thái tồn</option>
|
||||
<option
|
||||
v-for="status in statuses"
|
||||
:key="status"
|
||||
:value="status"
|
||||
>
|
||||
{{ status }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed-grid has-3-cols">
|
||||
<div class="grid">
|
||||
<div
|
||||
:class="['cell', selectedInvItem ? 'is-col-span-2' : 'is-col-span-3']"
|
||||
>
|
||||
<div class="card is-clipped">
|
||||
<div class="card-content p-0">
|
||||
<p class="p-5 fs-17 font-semibold is-flex is-align-items-center is-gap-1">
|
||||
<Icon name="material-symbols:deployed-code-outline" :size="22" />
|
||||
<span>Danh sách tồn kho ({{ filteredInvItems.length }})</span>
|
||||
</p>
|
||||
<table class="table is-fullwidth is-hoverable fs-13">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="font-semibold">Sản phẩm</th>
|
||||
<th class="font-semibold">SKU</th>
|
||||
<th class="font-semibold">Kho</th>
|
||||
<th class="font-semibold">Vị trí</th>
|
||||
<th class="font-semibold has-text-right">Tồn</th>
|
||||
<th class="font-semibold has-text-right">Đặt trước</th>
|
||||
<th class="font-semibold has-text-right">Khả dụng</th>
|
||||
<th class="font-semibold">Batch</th>
|
||||
<th class="font-semibold">Hạn sử dụng</th>
|
||||
<th class="font-semibold">Trạng thái</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<InventoryRow
|
||||
v-for="invItem in filteredInvItems"
|
||||
:key="invItem.id"
|
||||
:invItem="invItem"
|
||||
:selected="invItem.id === selectedInvItem?.id"
|
||||
@selectInvItem="(id) => {
|
||||
selectedInvItem = filteredInvItems.find(item => item.id === id);
|
||||
}"
|
||||
@unselect="selectedInvItem = null"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SelectedInvItem
|
||||
:invItem="selectedInvItem"
|
||||
@unselect="selectedInvItem = null"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
165
app/components/inventory/SelectedInvItem.vue
Normal file
165
app/components/inventory/SelectedInvItem.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
invItem: Object
|
||||
});
|
||||
|
||||
const { $dayjs, $numtoString } = useNuxtApp();
|
||||
const emit = defineEmits('unselect');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="invItem" class="cell relative fs-14">
|
||||
<div class="card">
|
||||
<button
|
||||
@click="emit('unselect')"
|
||||
class="button is-white rounded-full has-text-grey absolute size-8 is-flex is-justify-content-center is-align-items-center"
|
||||
style="right: 0.5rem; top: 0.5rem;"
|
||||
>
|
||||
<span class="icon">
|
||||
<Icon name="material-symbols:close-rounded" :size="22" />
|
||||
</span>
|
||||
</button>
|
||||
<div class="card-content is-clipped">
|
||||
<div class="py-3 sticky top-0">
|
||||
<p class="fs-17 font-semibold">Chi tiết sản phẩm</p>
|
||||
</div>
|
||||
<hr class="m-0" />
|
||||
<div style="max-height: 600px; overflow-y: scroll">
|
||||
<div>
|
||||
<div class="is-flex is-gap-2 is-align-items-center">
|
||||
<div class="is-flex is-gap-2">
|
||||
<div class="has-background-purple size-12 rounded-md is-flex is-justify-content-center is-align-items-center">
|
||||
<Icon
|
||||
name="material-symbols:deployed-code-outline"
|
||||
:size="24"
|
||||
class="has-text-white"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold mb-1">{{ invItem.name }}</p>
|
||||
<p class="has-text-grey-40">SKU: {{ invItem.sku }}</p>
|
||||
<p class="fs-12 has-text-grey">{{ invItem.category }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-flex is-gap-1 mt-4">
|
||||
<div class="px-4 py-3 is-flex-grow-1 rounded-md has-background-blue-95 has-text-blue-40">
|
||||
<p class="fs-13">Giá đơn vị</p>
|
||||
<p class="fs-16 font-semibold">{{ $numtoString(invItem.unit_price, { hasUnit: true }) }}</p>
|
||||
</div>
|
||||
<div class="px-4 py-3 is-flex-grow-1 rounded-md has-background-purple-90 has-text-purple-40">
|
||||
<p class="fs-13">Tổng giá trị</p>
|
||||
<p class="fs-16 font-semibold">{{ $numtoString(invItem.total, { hasUnit: true }) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<p class="fs-15 font-semibold mb-4">Tóm tắt tồn kho</p>
|
||||
<div class="is-flex is-flex-direction-column">
|
||||
<div class="py-2 is-flex is-justify-content-space-between">
|
||||
<p class="has-text-grey-40">Tồn hiện tại</p>
|
||||
<p class="fs-14 font-semibold">{{ invItem.stock }}</p>
|
||||
</div>
|
||||
<hr class="my-1 has-background-grey-95" />
|
||||
<div class="py-2 is-flex is-justify-content-space-between">
|
||||
<p class="has-text-grey-40">Đặt trước</p>
|
||||
<p class="fs-14 font-semibold has-text-orange">{{ invItem.preorder }}</p>
|
||||
</div>
|
||||
<hr class="my-1 has-background-grey-95" />
|
||||
<div class="py-2 is-flex is-justify-content-space-between">
|
||||
<p class="has-text-grey-40">Khả dụng</p>
|
||||
<p class="fs-14 font-semibold has-text-green">{{ invItem.available }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<p class="fs-15 font-semibold mb-4">Vị trí lưu kho</p>
|
||||
<div class="p-4 rounded-md has-background-grey-95">
|
||||
<div class="is-flex is-gap-1">
|
||||
<Icon name="material-symbols:location-on-outline-rounded" :size="20" />
|
||||
<div>
|
||||
<p>{{ invItem.storage }}</p>
|
||||
<p class="mt-1 fs-13 is-family-monospace">{{ invItem.storage__position }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<p class="fs-15 font-semibold mb-4">Thông tin lô hàng</p>
|
||||
<div class="p-4 rounded-md has-background-grey-95">
|
||||
<p class="fs-13 has-text-grey">Mã lô</p>
|
||||
<p class="mt-1 is-family-monospace">{{ invItem.batch }}</p>
|
||||
</div>
|
||||
<div class="p-4 mt-4 rounded-md has-background-grey-95">
|
||||
<div class="is-flex is-gap-1 is-align-items-center">
|
||||
<Icon name="material-symbols:calendar-today-outline-rounded" :size="18"
|
||||
class="has-text-grey-50"
|
||||
/>
|
||||
<p class="fs-13 has-text-grey">Hạn sử dụng</p>
|
||||
</div>
|
||||
<p class="mt-1">{{ $dayjs(invItem.expired).format('LL') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<p class="fs-15 font-semibold mb-4">Lịch sử di chuyển</p>
|
||||
<div
|
||||
v-for="move in invItem.moveHistory"
|
||||
:key="move.id"
|
||||
class="p-4 mb-4 rounded-md has-background-grey-95 has-text-grey-40 fs-12"
|
||||
>
|
||||
<div class="is-flex is-justify-content-space-between mb-1">
|
||||
<div class="is-flex is-gap-1 is-align-items-center">
|
||||
<div :class="[
|
||||
'p-1 rounded-sm is-flex is-align-items-center',
|
||||
`has-background-${move.delta > 0 ? 'success' : 'danger'}-soft`
|
||||
]">
|
||||
<Icon
|
||||
:name="move.delta > 0 ? 'ph:chart-line-up' : 'ph:chart-line-down'"
|
||||
:size="18"
|
||||
:class="`has-text-${move.delta > 0 ? 'success' : 'danger'}-40`"
|
||||
/>
|
||||
</div>
|
||||
<p :class="`has-text-${move.delta > 0 ? 'success' : 'danger'}-40`">{{ move.type__name }}</p>
|
||||
</div>
|
||||
<p
|
||||
:class="['fs-14 font-semibold', `has-text-${move.delta > 0 ? 'success' : 'danger'}-40`]"
|
||||
>{{ new Intl.NumberFormat('en-US', { signDisplay: 'exceptZero' }).format(move.delta) }}</p>
|
||||
</div>
|
||||
<p class="is-flex is-gap-0.5 is-align-items-center">
|
||||
<span>{{ $dayjs(move.date).format('L') }}</span>
|
||||
<span>•</span>
|
||||
<span class="is-family-monospace">{{ move.code }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{ move.from__code ? 'Từ' : 'Đến' }}</span>
|
||||
<span>: </span>
|
||||
<span>{{ move.from__code || move.to__code }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="mt-6">
|
||||
<p class="fs-15 font-semibold mb-4">Thao tác nhanh</p>
|
||||
<div class="fixed-grid">
|
||||
<div class="grid">
|
||||
<button class="button fs-13 is-primary">Điều chỉnh</button>
|
||||
<button class="button fs-13">Chuyển kho</button>
|
||||
<button class="button fs-13">Xem báo cáo</button>
|
||||
<button class="button fs-13">In nhãn</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.card-content {
|
||||
padding: 0; /* 1.5rem */
|
||||
|
||||
> div {
|
||||
padding: var(--bulma-card-content-padding);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -47,8 +47,8 @@ const highlights = [
|
||||
v-bind="highlight"
|
||||
/>
|
||||
</div>
|
||||
<OrderPipeline />
|
||||
<OrdersTable />
|
||||
</div>
|
||||
<OrderPipeline />
|
||||
<OrdersTable />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -483,7 +483,7 @@ function toggleStatus(id) {
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="is-flex is-gap-2 is-align-items-center">
|
||||
<div class="field m-0">
|
||||
<div class="field is-flex-grow-1 m-0">
|
||||
<p class="control has-icons-left">
|
||||
<input v-model="input" class="input" type="text" placeholder="Tìm kiếm mã đơn, khách hàng..." />
|
||||
<span class="icon is-small is-left">
|
||||
|
||||
Reference in New Issue
Block a user