|
|
@@ -25,7 +25,9 @@
|
|
|
<tr class="bg-muted/30 border-b border-border/50 text-[10px] font-bold uppercase tracking-wider text-muted-foreground">
|
|
|
<th class="p-4">{{ t("admin.fields.material") }}</th>
|
|
|
<th class="p-4">{{ t("admin.fields.colors") }}</th>
|
|
|
- <th class="p-4 text-center">{{ t("admin.fields.quantity") }}</th>
|
|
|
+ <th class="p-4 text-center">{{ t("admin.fields.unitMass") }}</th>
|
|
|
+ <th class="p-4 text-center">{{ t("admin.fields.unitsCount") }}</th>
|
|
|
+ <th class="p-4 text-center">{{ t("admin.fields.quantity") }} (kg)</th>
|
|
|
<th class="p-4 text-center">{{ t("admin.fields.status") }}</th>
|
|
|
<th class="p-4 text-right">{{ t("admin.labels.actions") }}</th>
|
|
|
</tr>
|
|
|
@@ -45,7 +47,18 @@
|
|
|
</div>
|
|
|
</td>
|
|
|
<td class="p-4 text-center">
|
|
|
- <span class="text-sm font-mono font-bold" :class="item.quantity <= 0 ? 'text-rose-500' : 'text-emerald-500'">
|
|
|
+ <span class="text-xs font-mono">{{ item.unit_mass }}</span>
|
|
|
+ </td>
|
|
|
+ <td class="p-4 text-center">
|
|
|
+ <div class="flex items-center justify-center gap-2">
|
|
|
+ <span class="text-xs font-bold">{{ item.units_count }}</span>
|
|
|
+ <button v-if="item.units_count > 0" @click="handleDeduct(item)" class="p-1 hover:bg-rose-500/10 rounded-md text-rose-500 transition-colors" title="Deduct 1 unit">
|
|
|
+ <Minus class="w-3 h-3" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td class="p-4 text-center">
|
|
|
+ <span class="text-sm font-mono font-bold" :class="item.quantity <= 0.1 ? 'text-rose-500' : 'text-emerald-500'">
|
|
|
{{ item.quantity }}
|
|
|
</span>
|
|
|
</td>
|
|
|
@@ -102,8 +115,19 @@
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
<div class="space-y-1">
|
|
|
- <label class="text-[10px] font-black uppercase text-muted-foreground ml-1 tracking-widest">{{ t('admin.fields.quantity') }}</label>
|
|
|
- <input v-model.number="form.quantity" type="number" step="0.1" required class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-2 ring-primary/20 outline-none" />
|
|
|
+ <label class="text-[10px] font-black uppercase text-muted-foreground ml-1 tracking-widest">{{ t('admin.fields.unitMass') }}</label>
|
|
|
+ <input v-model.number="form.unit_mass" type="number" step="0.001" required class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-2 ring-primary/20 outline-none" />
|
|
|
+ </div>
|
|
|
+ <div class="space-y-1">
|
|
|
+ <label class="text-[10px] font-black uppercase text-muted-foreground ml-1 tracking-widest">{{ t('admin.fields.unitsCount') }}</label>
|
|
|
+ <input v-model.number="form.units_count" type="number" required class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-2 ring-primary/20 outline-none" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="grid grid-cols-2 gap-4">
|
|
|
+ <div class="space-y-1">
|
|
|
+ <label class="text-[10px] font-black uppercase text-muted-foreground ml-1 tracking-widest">{{ t('admin.fields.quantity') }} (kg)</label>
|
|
|
+ <input v-model.number="form.quantity" type="number" step="0.001" required class="w-full bg-background border border-border/50 rounded-xl px-4 py-3 text-sm focus:ring-2 ring-primary/20 outline-none" />
|
|
|
</div>
|
|
|
<div class="space-y-1 flex flex-col justify-end pb-2">
|
|
|
<label class="flex items-center gap-2 cursor-pointer group">
|
|
|
@@ -142,7 +166,7 @@
|
|
|
<script setup lang="ts">
|
|
|
import { ref, reactive, onMounted } from "vue";
|
|
|
import { useI18n } from "vue-i18n";
|
|
|
-import { Package, Plus, PackageOpen, Check, Edit2, Trash2 } from "lucide-vue-next";
|
|
|
+import { Package, Plus, PackageOpen, Check, Edit2, Trash2, Minus } from "lucide-vue-next";
|
|
|
import { toast } from "vue-sonner";
|
|
|
import {
|
|
|
adminGetWarehouseStock,
|
|
|
@@ -166,11 +190,19 @@ const editingId = ref<number | null>(null);
|
|
|
const form = reactive({
|
|
|
material_id: 0,
|
|
|
color_name: "",
|
|
|
- quantity: 1,
|
|
|
+ quantity: 1.0,
|
|
|
+ unit_mass: 1.0,
|
|
|
+ units_count: 1,
|
|
|
is_active: true,
|
|
|
notes: ""
|
|
|
});
|
|
|
|
|
|
+watch(() => [form.unit_mass, form.units_count], ([m, c]) => {
|
|
|
+ if (!editingId.value) {
|
|
|
+ form.quantity = Number((m * c).toFixed(3));
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
fetchStock();
|
|
|
if (props.materials.length > 0) {
|
|
|
@@ -188,6 +220,25 @@ async function fetchStock() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+async function handleDeduct(item: any) {
|
|
|
+ if (item.units_count <= 0) return;
|
|
|
+
|
|
|
+ const newCount = item.units_count - 1;
|
|
|
+ const newTotal = Number((Math.max(0, item.quantity - item.unit_mass)).toFixed(3));
|
|
|
+
|
|
|
+ try {
|
|
|
+ await adminUpdateWarehouseStock(item.id, {
|
|
|
+ units_count: newCount,
|
|
|
+ quantity: newTotal
|
|
|
+ });
|
|
|
+ item.units_count = newCount;
|
|
|
+ item.quantity = newTotal;
|
|
|
+ toast.success(t('admin.toasts.materialSaved'));
|
|
|
+ } catch (err: any) {
|
|
|
+ toast.error(err.message);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
async function toggleStatus(item: any) {
|
|
|
try {
|
|
|
await adminUpdateWarehouseStock(item.id, { is_active: !item.is_active });
|
|
|
@@ -204,6 +255,8 @@ function handleEdit(item: any) {
|
|
|
material_id: item.material_id,
|
|
|
color_name: item.color_name,
|
|
|
quantity: item.quantity,
|
|
|
+ unit_mass: item.unit_mass,
|
|
|
+ units_count: item.units_count,
|
|
|
is_active: item.is_active,
|
|
|
notes: item.notes || ""
|
|
|
});
|
|
|
@@ -211,7 +264,7 @@ function handleEdit(item: any) {
|
|
|
}
|
|
|
|
|
|
async function handleDelete(id: number) {
|
|
|
- if (!confirm(t('admin.questions.deletePhoto'))) return; // Repurposing confirm
|
|
|
+ if (!confirm(t('admin.questions.deletePhoto'))) return;
|
|
|
try {
|
|
|
await adminDeleteWarehouseStock(id);
|
|
|
toast.success(t('admin.toasts.materialDeleted'));
|