- {/* فیلترهای تگ (اسکرول افقی) */}
+
+
لیست قفسهها ({filteredLocations.length})
+
+
+ {/* Tag Filters */}
{floors.length > 0 && (
-
+
@@ -154,7 +213,7 @@ export default function AdminLocations() {
@@ -162,18 +221,83 @@ export default function AdminLocations() {
)}
-
- {filteredLocations.map((loc) => (
-
-
{loc.code}
-
طبقه {loc.floor} | منطقه {loc.region} | قطاع {loc.sector} | ردیف {loc.row}
+ {/* Cards */}
+
+ {filteredLocations.map((loc) => {
+ const hasData = loc._count?.countings > 0;
+ return (
+
+
+
+
+
+
+ {loc.code}
+
+ طبقه {loc.floor} • منطقه {loc.region} • قطاع {loc.sector}
+
+
+
+
+
+ {!hasData ? (
+ <>
+
+
+ >
+ ) : (
+
+
+ {loc._count.countings} کالا
+
+ )}
+
+
+ );
+ })}
+
+ {filteredLocations.length === 0 && (
+
+
+
+
+
هیچ قفسهای یافت نشد
+
با فرم بالا یک قفسه جدید ثبت کنید
- ))}
- {filteredLocations.length === 0 &&
موردی یافت نشد.}
+ )}
-
+
+ {/* Minimal Toast Notification */}
+
+ {toast.show && (
+
+ {toast.isError ? : }
+ {toast.message}
+
+ )}
+
);
}
diff --git a/src/app/api/locations/[id]/route.js b/src/app/api/locations/[id]/route.js
new file mode 100644
index 0000000..3d11fa0
--- /dev/null
+++ b/src/app/api/locations/[id]/route.js
@@ -0,0 +1,59 @@
+import prisma from '@/lib/prisma';
+import { NextResponse } from 'next/server';
+
+export async function DELETE(req, { params }) {
+ try {
+ const { id: idParam } = await params;
+ const id = parseInt(idParam, 10);
+ const location = await prisma.location.findUnique({
+ where: { id },
+ include: { _count: { select: { countings: true } } }
+ });
+
+ if (!location) return NextResponse.json({ error: 'قفسه یافت نشد' }, { status: 404 });
+ if (location._count.countings > 0) {
+ return NextResponse.json({ error: 'این قفسه دارای کالای شمرده شده است و قابل حذف نیست.' }, { status: 400 });
+ }
+
+ await prisma.location.delete({ where: { id } });
+ return NextResponse.json({ success: true });
+ } catch (error) {
+ return NextResponse.json({ error: 'خطا در حذف قفسه' }, { status: 500 });
+ }
+}
+
+export async function PUT(req, { params }) {
+ try {
+ const { id: idParam } = await params;
+ const id = parseInt(idParam, 10);
+ const { code } = await req.json();
+
+ const location = await prisma.location.findUnique({
+ where: { id },
+ include: { _count: { select: { countings: true } } }
+ });
+
+ if (!location) return NextResponse.json({ error: 'قفسه یافت نشد' }, { status: 404 });
+ if (location._count.countings > 0) {
+ return NextResponse.json({ error: 'این قفسه دارای کالا است و قابل ویرایش نیست.' }, { status: 400 });
+ }
+
+ const regex = /^([A-Za-z]+)(\d+)([A-Za-z]+)(\d+)$/;
+ const match = code.toUpperCase().match(regex);
+ if (!match) return NextResponse.json({ error: 'فرمت کد نامعتبر است. مثال: C2F2' }, { status: 400 });
+
+ const [, floor, regionStr, sector, rowStr] = match;
+ const region = parseInt(regionStr, 10);
+ const row = parseInt(rowStr, 10);
+
+ const updated = await prisma.location.update({
+ where: { id },
+ data: { code: code.toUpperCase(), floor, region, sector, row }
+ });
+
+ return NextResponse.json({ success: true, location: updated });
+ } catch (error) {
+ if (error.code === 'P2002') return NextResponse.json({ error: 'این کد قفسه از قبل وجود دارد' }, { status: 400 });
+ return NextResponse.json({ error: 'خطا در ویرایش قفسه' }, { status: 500 });
+ }
+}
diff --git a/src/app/api/locations/route.js b/src/app/api/locations/route.js
index 1687cc0..6cf0cfa 100644
--- a/src/app/api/locations/route.js
+++ b/src/app/api/locations/route.js
@@ -4,6 +4,7 @@ import { NextResponse } from 'next/server';
export async function GET() {
try {
const locations = await prisma.location.findMany({
+ include: { _count: { select: { countings: true } } },
orderBy: [{ floor: 'asc' }, { region: 'asc' }, { sector: 'asc' }, { row: 'asc' }]
});
return NextResponse.json(locations);
diff --git a/src/app/layout.js b/src/app/layout.js
index 50bbf84..309d466 100644
--- a/src/app/layout.js
+++ b/src/app/layout.js
@@ -9,8 +9,8 @@ export const viewport = {
}
export const metadata = {
- title: 'پردیس رایانه - انبارگردانی',
- description: 'اپلیکیشن انبارگردانی پردیس',
+ title: 'سیستم انبارداری',
+ description: 'اپلیکیشن مدیریت انبار',
manifest: "/manifest.json",
appleWebApp: {
capable: true,
diff --git a/src/app/page.js b/src/app/page.js
index d9d897f..d840bcd 100644
--- a/src/app/page.js
+++ b/src/app/page.js
@@ -96,56 +96,13 @@ export default function EntryPage() {
className="absolute inset-0 z-50 flex flex-col items-center justify-center bg-gray-50/50 backdrop-blur-md"
>
-
-
-
-
-
- انبارگردانی هوشمند
-
-
-
- مدیریت حرفهای انبار و قفسهها
-
+
-
-
-
-
- H U K A
-
- طراحی توسط هوکا
-
-
-
-
) : showInstall ? (
-
-

+
+
@@ -235,7 +192,7 @@ export default function EntryPage() {
className="flex flex-col items-center justify-center flex-1 pt-10"
>
-

+
خوش آمدید
diff --git a/src/app/scan/page.js b/src/app/scan/page.js
index bee7aa5..de353c8 100644
--- a/src/app/scan/page.js
+++ b/src/app/scan/page.js
@@ -1,8 +1,10 @@
'use client';
-import { useState, useEffect } from 'react';
+import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Header from '@/components/Header';
import dynamic from 'next/dynamic';
+import { motion } from 'framer-motion';
+import { ScanLine, Search, Box, X, ChevronDown, CheckCircle2 } from 'lucide-react';
const Scanner = dynamic(() => import('@yudiel/react-qr-scanner').then(mod => mod.Scanner), { ssr: false });
export default function ScanPage() {
@@ -26,7 +28,7 @@ export default function ScanPage() {
setCameraEnabled(false);
setTimeout(() => {
router.push(`/counting?code=${scannedValue}&warehouse=${warehouse}`);
- }, 1500);
+ }, 500);
}
};
@@ -34,76 +36,113 @@ export default function ScanPage() {
console.error(error);
const msg = error?.message || error?.name || '';
if (msg.includes('Requested device not found') || msg.includes('NotFoundError') || msg.includes('device not found')) {
- setCamError('هیچ دوربینی روی این دستگاه یافت نشد. لطفاً از لپتاپ یا موبایل استفاده کنید.');
+ setCamError('دوربینی روی این دستگاه یافت نشد. لطفاً از لپتاپ یا موبایل استفاده کنید.');
} else {
- setCamError(msg || 'خطا در دسترسی به دوربین. آیا از HTTPS یا localhost استفاده میکنید؟');
+ setCamError(msg || 'خطا در دسترسی به دوربین.');
}
};
return (
-
-
+
+
-
+
-
- {cameraEnabled ? (
-
- {camError ? (
-
{camError}
- ) : (
-
- )}
-
-
کد کالا را اسکن یا وارد کنید
-
-
setCode(e.target.value)}
- placeholder="ورود دستی کد"
- className="w-full max-w-xs text-center text-3xl p-4 border rounded-lg focus:ring-2 focus:ring-purple-500 outline-none"
- />
-
-
-
-
- شمارش
-
+ {/* Manual Input */}
+
+
+
+
+
+
setCode(e.target.value)}
+ placeholder="ورود دستی کد کالا..."
+ className="w-full pl-12 pr-5 py-4 bg-white border border-gray-200 rounded-[24px] focus:outline-none focus:border-indigo-500 focus:ring-4 focus:ring-indigo-50 transition-all text-left text-lg font-bold text-gray-800 placeholder-gray-300 shadow-sm"
+ />
+
+
+
+
+
+
+
+
+
+
+ {code ? : }
+ شروع شمارش کالا
+
+
+
);