erp-platform/ui/src/views/warehouse/components/WarehouseReceipt.tsx

1507 lines
68 KiB
TypeScript
Raw Normal View History

2025-09-15 09:31:47 +00:00
import React, { useState } from "react";
import { MovementStatusEnum, MovementTypeEnum } from "../../../types/mm";
import {
FaSearch,
FaPlus,
FaEdit,
FaTrash,
FaBox,
FaDownload,
FaUpload,
FaEye,
FaArrowCircleDown,
FaSave,
FaTimes,
FaInfoCircle,
FaWarehouse,
FaBarcode,
FaCalculator,
} from "react-icons/fa";
import { mockWarehouses } from "../../../mocks/mockWarehouses";
import { mockStockMovements } from "../../../mocks/mockStockMovements";
import { mockMaterials } from "../../../mocks/mockMaterials";
import { mockEmployees } from "../../../mocks/mockEmployees";
import Widget from "../../../components/common/Widget";
import {
getMovementStatusColor,
getMovementStatusText,
getMovementStatusIcon,
} from "../../../utils/erp";
const WarehouseReceipt: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [statusFilter, setStatusFilter] = useState<MovementStatusEnum | "">("");
const [selectedMovement, setSelectedMovement] = useState<string>("");
const [showReceiptForm, setShowReceiptForm] = useState(false);
const [editingMovement, setEditingMovement] = useState<string | null>(null);
// Form state - Header data for the entire movement
const [headerData, setHeaderData] = useState({
movementNumber: "",
referenceDocument: "",
referenceType: "Purchase Order",
reason: "",
movementDate: new Date().toISOString().split("T")[0],
performedBy: "",
approvedBy: "",
description: "",
status: MovementStatusEnum.Planned,
});
// Materials list for the movement
const [materialsList, setMaterialsList] = useState([
{
id: Date.now().toString(),
materialId: "",
warehouseId: "",
zoneId: "",
locationId: "",
quantity: "",
lotNumber: "",
unitPrice: "",
},
]);
const resetForm = () => {
setHeaderData({
movementNumber: "",
referenceDocument: "",
referenceType: "Purchase Order",
reason: "",
movementDate: new Date().toISOString().split("T")[0],
performedBy: "",
approvedBy: "",
description: "",
status: MovementStatusEnum.Planned,
});
setMaterialsList([
{
id: Date.now().toString(),
materialId: "",
warehouseId: "",
zoneId: "",
locationId: "",
quantity: "",
lotNumber: "",
unitPrice: "",
},
]);
setEditingMovement(null);
};
const handleHeaderInputChange = (field: string, value: string) => {
setHeaderData((prev) => ({
...prev,
[field]: value,
}));
};
const handleMaterialInputChange = (
materialId: string,
field: string,
value: string
) => {
setMaterialsList((prev) =>
prev.map((material) =>
material.id === materialId ? { ...material, [field]: value } : material
)
);
};
const addMaterial = () => {
const newMaterial = {
id: Date.now().toString(),
materialId: "",
warehouseId: "",
zoneId: "",
locationId: "",
quantity: "",
lotNumber: "",
unitPrice: "",
};
setMaterialsList((prev) => [...prev, newMaterial]);
};
const removeMaterial = (materialId: string) => {
setMaterialsList((prev) =>
prev.filter((material) => material.id !== materialId)
);
};
const handleSubmit = () => {
// In a real application, this would submit to an API
console.log("Submitting receipt form:", { headerData, materialsList });
setShowReceiptForm(false);
resetForm();
// Here you would typically update the mock data or call an API
};
const handleEdit = (movementId: string) => {
// Find all movements with the same movement number
const movement = mockStockMovements.find((m) => m.id === movementId);
if (movement) {
const movementsWithSameNumber = mockStockMovements.filter(
(m) => m.movementNumber === movement.movementNumber
);
// Set header data from the first movement
setHeaderData({
movementNumber: movement.movementNumber,
referenceDocument: movement.referenceDocument || "",
referenceType: movement.referenceType || "Purchase Order",
reason: movement.reason || "",
movementDate: movement.movementDate.toISOString().split("T")[0],
performedBy: movement.performedBy || "",
approvedBy: movement.approvedBy || "",
description: movement.description || "",
status: movement.status || MovementStatusEnum.Planned,
});
// Set materials list from all movements with the same number
const materialsData = movementsWithSameNumber.map((m, index) => ({
id: (Date.now() + index).toString(),
materialId: m.materialId,
warehouseId: m.toWarehouseId || "",
zoneId: m.toZoneId || "",
locationId: m.toLocationId || "",
quantity: m.quantity.toString(),
lotNumber: m.lotNumber || "",
unitPrice: (m.material?.costPrice || 0).toString(),
}));
setMaterialsList(materialsData);
setEditingMovement(movementId);
setShowReceiptForm(true);
}
};
const filteredMovements = mockStockMovements.filter((movement) => {
const material = movement.material;
const matchesSearch =
movement.movementNumber
.toLowerCase()
.includes(searchTerm.toLowerCase()) ||
material?.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
material?.code?.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus =
statusFilter === "" || movement.status === statusFilter;
const matchesType = movement.movementType === MovementTypeEnum.GoodsReceipt;
return matchesSearch && matchesStatus && matchesType;
});
const MovementDetailModal = () => {
const movement = mockStockMovements.find((m) => m.id === selectedMovement);
if (!selectedMovement || !movement) return null;
// Aynı hareket numarasına sahip tüm malzemeleri bul
const relatedMovements = mockStockMovements.filter(
(m) => m.movementNumber === movement.movementNumber
);
const StatusIcon = getMovementStatusIcon(movement.status!);
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
onClick={() => setSelectedMovement("")}
/>
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle w-full max-w-7xl mx-4 sm:mx-auto">
<div className="bg-white ">
{/* Modal Header */}
<div className="bg-gradient-to-r from-blue-600 to-blue-700 px-4 sm:px-6 py-3">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="p-2 bg-white bg-opacity-20 rounded-lg">
<FaArrowCircleDown className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="text-lg sm:text-xl font-semibold text-white">
{movement.movementNumber} - Giriş Detayları
</h3>
<p className="text-blue-100 text-sm">
{relatedMovements.length} malzeme {" "}
{movement.movementDate.toLocaleDateString("tr-TR")}
</p>
</div>
</div>
<button
onClick={() => setSelectedMovement("")}
className="text-white hover:text-blue-100 transition-colors p-2 hover:bg-white hover:bg-opacity-20 rounded-lg"
>
<FaTimes className="w-6 h-6" />
</button>
</div>
</div>
{/* Modal Content */}
<div className="px-4 sm:px-6 py-4 max-h-[70vh] overflow-y-auto">
{/* Movement Summary */}
<div className="bg-gradient-to-r from-gray-50 to-gray-100 rounded-xl p-3 sm:p-3 mb-4 border border-gray-200">
<div className="flex items-center justify-between mb-3">
<h4 className="text-base font-semibold text-gray-900 flex items-center">
<FaInfoCircle className="w-5 h-5 text-gray-600 mr-2" />
Hareket Özeti
</h4>
<div
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${getMovementStatusColor(
movement.status!
)}`}
>
<StatusIcon className="w-4 h-4 mr-1" />
{getMovementStatusText(movement.status!)}
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 text-sm">
<div className="bg-white rounded-lg p-3 border border-gray-200">
<div className="text-gray-600 font-medium mb-1">
Hareket No
</div>
<div className="text-gray-900 font-semibold">
{movement.movementNumber}
</div>
</div>
<div className="bg-white rounded-lg p-3 border border-gray-200">
<div className="text-gray-600 font-medium mb-1">
Tarih
</div>
<div className="text-gray-900 font-semibold">
{movement.movementDate.toLocaleDateString("tr-TR")}
</div>
</div>
<div className="bg-white rounded-lg p-3 border border-gray-200">
<div className="text-gray-600 font-medium mb-1">
Referans
</div>
<div className="text-gray-900 font-semibold">
{movement.referenceDocument || "-"}
</div>
</div>
<div className="bg-white rounded-lg p-3 border border-gray-200">
<div className="text-gray-600 font-medium mb-1">
İşlem Yapan
</div>
<div className="text-gray-900 font-semibold">
{movement.performedBy || "-"}
</div>
</div>
</div>
{movement.description && (
<div className="mt-4 bg-white rounded-lg p-3 border border-gray-200">
<div className="text-gray-600 font-medium mb-1">
ıklama
</div>
<div className="text-gray-900">
{movement.description}
</div>
</div>
)}
</div>
{/* Materials List */}
<div className="space-y-4">
<h4 className="text-base font-semibold text-gray-900 flex items-center">
<FaBox className="w-4 h-4 text-gray-600 mr-2" />
Giriş Yapılan Malzemeler ({relatedMovements.length})
</h4>
<div className="grid gap-4">
{relatedMovements.map((mov, index) => {
const warehouse = mockWarehouses.find(
(w) => w.id === mov.toWarehouseId
);
const zone = mockWarehouses
.flatMap((w) => w.zones || [])
.find((z) => z.id === mov.toZoneId);
const location = mockWarehouses
.flatMap((w) => w.locations || [])
.find((l) => l.id === mov.toLocationId);
return (
<div
key={mov.id}
className="bg-white border border-gray-200 rounded-xl overflow-hidden hover:shadow-lg transition-shadow"
>
{/* Material Header */}
<div className="bg-gray-50 px-4 py-2 border-b border-gray-200">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
<span className="text-blue-600 font-semibold text-sm">
{index + 1}
</span>
</div>
<div>
<h5 className="font-semibold text-gray-900">
{mov.material?.code} - {mov.material?.name}
</h5>
<p className="text-sm text-gray-600">
{mov.material?.materialType?.name}
</p>
</div>
</div>
<div className="text-right">
<div className="text-lg font-bold text-blue-600">
{mov.quantity} Adet
</div>
<div className="text-sm text-gray-600">
Birim Fiyat:
{mov.material?.costPrice?.toFixed(2)}
</div>
</div>
</div>
</div>
{/* Material Details */}
<div className="p-3">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{/* Warehouse Info */}
<div className="space-y-2">
<h6 className="font-medium text-sm text-gray-700 flex items-center">
<FaWarehouse className="w-4 h-4 mr-2 text-gray-500" />
Depo Bilgileri
</h6>
<div className="bg-gray-50 rounded-lg p-3 space-y-1 text-sm">
<div>
<span className="font-medium">Depo:</span>{" "}
{warehouse?.name}
</div>
<div>
<span className="font-medium">Bölge:</span>{" "}
{zone?.name || "-"}
</div>
<div>
<span className="font-medium">
Lokasyon:
</span>{" "}
{location?.name || "-"}
</div>
</div>
</div>
{/* Stock Info */}
<div className="space-y-2">
<h6 className="font-medium text-sm text-gray-700 flex items-center">
<FaBarcode className="w-4 h-4 mr-2 text-gray-500" />
Stok Bilgileri
</h6>
<div className="bg-gray-50 rounded-lg p-3 space-y-1 text-sm">
<div>
<span className="font-medium">Lot No:</span>{" "}
{mov.lotNumber || "-"}
</div>
<div>
<span className="font-medium">Miktar:</span>{" "}
{mov.quantity} Adet
</div>
<div>
<span className="font-medium">
Toplam Değer:
</span>{" "}
{(
(mov.material?.costPrice || 0) *
mov.quantity
).toFixed(2)}
</div>
</div>
</div>
{/* Additional Info */}
<div className="space-y-2">
<h6 className="font-medium text-sm text-gray-700 flex items-center">
<FaInfoCircle className="w-4 h-4 mr-2 text-gray-500" />
Ek Bilgiler
</h6>
<div className="bg-gray-50 rounded-lg p-3 space-y-1 text-sm">
<div>
<span className="font-medium">
Malzeme Grubu:
</span>{" "}
{mov.material?.materialGroup?.name || "-"}
</div>
<div>
<span className="font-medium">
Referans Tipi:
</span>{" "}
{mov.referenceType || "-"}
</div>
<div>
<span className="font-medium">Sebep:</span>{" "}
{mov.reason || "-"}
</div>
</div>
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
{/* Modal Footer */}
<div className="bg-gray-50 px-4 sm:px-6 py-3 border-t border-gray-200">
<div className="flex flex-col sm:flex-row justify-between items-center space-y-3 sm:space-y-0">
<div className="flex items-center space-x-4 text-sm text-gray-600">
<div className="flex items-center">
<FaBox className="w-4 h-4 mr-1" />
<span>{relatedMovements.length} malzeme</span>
</div>
<div className="flex items-center">
<FaCalculator className="w-4 h-4 mr-1" />
<span>
Toplam:
{relatedMovements
.reduce(
(sum, mov) =>
sum +
(mov.material?.costPrice || 0) * mov.quantity,
0
)
.toFixed(2)}
</span>
</div>
</div>
<button
onClick={() => setSelectedMovement("")}
className="bg-gray-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-gray-700 transition-colors flex items-center space-x-2"
>
<span>Kapat</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
return (
<div className="space-y-4 pt-2">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold text-gray-900">Stok Giriş</h2>
<p className="text-sm text-gray-600 mt-1">
Depoya malzeme giriş hareketlerini yönetin
</p>
</div>
<div className="flex gap-2">
<button
onClick={() => setShowReceiptForm(true)}
className="bg-blue-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center gap-2"
>
<FaPlus className="w-4 h-4" />
Yeni Giriş
</button>
<button className="bg-green-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-green-700 flex items-center gap-2">
<FaUpload className="w-4 h-4" />
İçe Aktar
</button>
<button className="bg-gray-600 text-white px-4 py-1.5 text-sm rounded-lg hover:bg-gray-700 flex items-center gap-2">
<FaDownload className="w-4 h-4" />
Dışa Aktar
</button>
</div>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<Widget
title="Bugün Giriş"
value={
mockStockMovements.filter(
(m) =>
m.movementDate.toDateString() === new Date().toDateString() &&
m.status === MovementStatusEnum.Completed &&
m.movementType === MovementTypeEnum.GoodsReceipt
).length
}
color="blue"
icon="FaArrowCircleDown"
/>
<Widget
title="Bekleyen"
value={
mockStockMovements.filter(
(m) =>
m.status === MovementStatusEnum.Planned &&
m.movementType === MovementTypeEnum.GoodsReceipt
).length
}
color="yellow"
icon="FaClock"
/>
<Widget
title="İşlemde"
value={
mockStockMovements.filter(
(m) =>
m.status === MovementStatusEnum.InProgress &&
m.movementType === MovementTypeEnum.GoodsReceipt
).length
}
color="blue"
icon="FaBox"
/>
<Widget
title="Tamamlanan"
value={
mockStockMovements.filter(
(m) =>
m.status === MovementStatusEnum.Completed &&
m.movementType === MovementTypeEnum.GoodsReceipt
).length
}
color="green"
icon="FaCheckCircle"
/>
</div>
{/* Filters */}
<div className="flex flex-col sm:flex-row gap-4">
<div className="relative flex-1">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
placeholder="Giriş ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 pr-4 py-1.5 text-sm w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<select
value={statusFilter}
onChange={(e) =>
setStatusFilter(e.target.value as MovementStatusEnum | "")
}
className="px-4 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="">Tüm Durumlar</option>
<option value={MovementStatusEnum.Planned}>Planlandı</option>
<option value={MovementStatusEnum.InProgress}>Devam Ediyor</option>
<option value={MovementStatusEnum.Completed}>Tamamlandı</option>
<option value={MovementStatusEnum.Cancelled}>İptal Edildi</option>
</select>
</div>
{/* Movements Table */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Hareket No
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Malzeme
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Depo / Bölge / Lokasyon
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Miktar / Lot No
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Referans
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tarih
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredMovements.map((movement) => {
const StatusIcon = getMovementStatusIcon(movement.status!);
return (
<tr key={movement.id} className="hover:bg-gray-50">
<td className="px-3 py-2 whitespace-nowrap">
<div className="flex items-center">
<div className="p-2 bg-blue-100 rounded-lg mr-3">
<FaArrowCircleDown className="w-4 h-4 text-blue-600" />
</div>
<div>
<div className="text-sm font-medium text-gray-900">
{movement.movementNumber}
</div>
<div className="text-sm text-gray-500">
{movement.performedBy}
</div>
</div>
</div>
</td>
<td className="px-3 py-2 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-gray-900">
{movement.material?.name}
</div>
<div className="text-sm text-gray-500">
{movement.material?.code}
</div>
</div>
</td>
<td className="px-3 py-2 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-gray-900">
{movement.toWarehouse?.name ||
movement.toWarehouse?.code}
</div>
<div className="text-sm text-gray-500">
{movement.toZone?.name && `${movement.toZone.name}`}
{movement.toLocation?.name &&
` / ${movement.toLocation.name}`}
</div>
{movement.toLocation?.locationCode && (
<div className="text-xs text-gray-400">
Kod: {movement.toLocation.locationCode}
</div>
)}
</div>
</td>
<td className="px-3 py-2 whitespace-nowrap">
<div className="text-sm text-gray-900">
{movement.quantity} {movement.unit?.code}
</div>
{movement.lotNumber && (
<div className="text-sm text-gray-500">
Lot: {movement.lotNumber}
</div>
)}
</td>
<td className="px-3 py-2 whitespace-nowrap">
<div className="text-sm text-gray-900">
{movement.referenceDocument}
</div>
<div className="text-sm text-gray-500">
{movement.referenceType}
</div>
</td>
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
{movement.movementDate.toLocaleDateString("tr-TR")}
</td>
<td className="px-3 py-2 whitespace-nowrap">
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getMovementStatusColor(
movement.status!
)}`}
>
<StatusIcon className="w-3 h-3 mr-1" />
{getMovementStatusText(movement.status!)}
</span>
</td>
<td className="px-3 py-2 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end gap-2">
<button
onClick={() => setSelectedMovement(movement.id)}
className="text-blue-600 hover:text-blue-900"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(movement.id)}
className="text-blue-600 hover:text-blue-900"
>
<FaEdit className="w-4 h-4" />
</button>
<button className="text-red-600 hover:text-red-900">
<FaTrash className="w-4 h-4" />
</button>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
{/* Movement Detail Modal */}
<MovementDetailModal />
{/* Receipt Form Modal */}
{showReceiptForm && (
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
onClick={() => {
setShowReceiptForm(false);
resetForm();
}}
/>
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle w-full max-w-7xl mx-4 sm:mx-auto">
<div className="bg-white px-4 sm:px-6 pt-5 pb-4 sm:p-6">
<div className="flex items-center justify-between mb-3">
<h3 className="text-base font-medium text-gray-900">
{editingMovement ? "Stok Giriş Düzenle" : "Yeni Stok Giriş"}
</h3>
<button
onClick={() => {
setShowReceiptForm(false);
resetForm();
}}
className="text-gray-400 hover:text-gray-600"
>
<FaTimes className="w-6 h-6" />
</button>
</div>
{/* Header Section - Compact and Responsive */}
<div className="bg-gray-50 rounded-lg p-3 mb-3">
<h4 className="text-sm font-medium text-gray-900 mb-2">
Genel Bilgiler
</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-3 text-sm">
{/* Movement Number */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Hareket No
</label>
<input
type="text"
value={headerData.movementNumber}
onChange={(e) =>
handleHeaderInputChange(
"movementNumber",
e.target.value
)
}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Otomatik"
disabled={!!editingMovement}
/>
</div>
{/* Movement Date */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Tarih *
</label>
<input
type="date"
value={headerData.movementDate}
onChange={(e) =>
handleHeaderInputChange(
"movementDate",
e.target.value
)
}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
/>
</div>
{/* Reference Document */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Referans Belge
</label>
<input
type="text"
value={headerData.referenceDocument}
onChange={(e) =>
handleHeaderInputChange(
"referenceDocument",
e.target.value
)
}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Belge no"
/>
</div>
{/* Reference Type */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Referans Tipi
</label>
<select
value={headerData.referenceType}
onChange={(e) =>
handleHeaderInputChange(
"referenceType",
e.target.value
)
}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
>
<option value="Purchase Order">
Satın Alma Siparişi
</option>
<option value="Production Order">Üretim Emri</option>
<option value="Transfer Order">Transfer Emri</option>
<option value="Return">İade</option>
<option value="Adjustment">Düzeltme</option>
<option value="Other">Diğer</option>
</select>
</div>
{/* Performed By */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
İşlem Yapan *
</label>
<select
value={headerData.performedBy}
onChange={(e) =>
handleHeaderInputChange("performedBy", e.target.value)
}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
>
<option value="">Çalışan Seçiniz</option>
{mockEmployees.map((employee) => (
<option key={employee.id} value={employee.fullName}>
{employee.fullName} - {employee.code}
</option>
))}
</select>
</div>
{/* Status */}
<div className="sm:col-span-1">
<label className="block text-xs font-medium text-gray-700 mb-1">
Durum
</label>
<select
value={headerData.status}
onChange={(e) =>
handleHeaderInputChange(
"status",
e.target.value as MovementStatusEnum
)
}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
>
<option value={MovementStatusEnum.Planned}>
Planlandı
</option>
<option value={MovementStatusEnum.InProgress}>
Devam Ediyor
</option>
<option value={MovementStatusEnum.Completed}>
Tamamlandı
</option>
</select>
</div>
{/* Description - Full width */}
<div className="col-span-1 sm:col-span-2 lg:col-span-3 xl:col-span-6">
<label className="block text-xs font-medium text-gray-700 mb-1">
ıklama
</label>
<textarea
value={headerData.description}
onChange={(e) =>
handleHeaderInputChange("description", e.target.value)
}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Hareket açıklaması"
rows={2}
/>
</div>
</div>
</div>
{/* Materials Section - Responsive */}
<div className="mb-2">
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-900">
Giriş Malzemeleri
{materialsList.length > 0 && (
<span className="ml-2 text-xs text-blue-600">
({materialsList.length} malzeme)
</span>
)}
</h4>
</div>
{/* Desktop Table View */}
<div className="hidden lg:block border border-gray-200 rounded-lg overflow-hidden">
<div className="bg-gray-50 px-2 py-2 border-b border-gray-200">
<div className="grid grid-cols-12 gap-2 text-xs font-medium text-gray-500 uppercase tracking-wider">
<div className="col-span-2">Malzeme</div>
<div className="col-span-2">Depo</div>
<div className="col-span-2">Zone</div>
<div className="col-span-2">Lokasyon</div>
<div className="col-span-1">Miktar</div>
<div className="col-span-1">B.Fiyat</div>
<div className="col-span-1">Lot No</div>
<div className="col-span-1">İşlem</div>
</div>
</div>
<div className="max-h-64 overflow-y-auto">
{materialsList.map((material) => {
const selectedMaterial = mockMaterials.find(
(m) => m.id === material.materialId
);
const selectedWarehouse = mockWarehouses.find(
(w) => w.id === material.warehouseId
);
const availableZones = selectedWarehouse?.zones || [];
const availableLocations =
selectedWarehouse?.locations?.filter(
(location) =>
!material.zoneId ||
location.zoneId === material.zoneId
) || [];
return (
<div
key={material.id}
className="px-2 py-2 border-b border-gray-100 last:border-b-0"
>
<div className="grid grid-cols-12 gap-2 items-center">
{/* Material Selection */}
<div className="col-span-2">
<select
value={material.materialId}
onChange={(e) =>
handleMaterialInputChange(
material.id,
"materialId",
e.target.value
)
}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
>
<option value="">Malzeme Seçiniz</option>
{mockMaterials.map((mat) => (
<option key={mat.id} value={mat.id}>
{mat.code} - {mat.name}
</option>
))}
</select>
</div>
{/* Warehouse Selection */}
<div className="col-span-2">
<select
value={material.warehouseId}
onChange={(e) => {
handleMaterialInputChange(
material.id,
"warehouseId",
e.target.value
);
handleMaterialInputChange(
material.id,
"zoneId",
""
);
handleMaterialInputChange(
material.id,
"locationId",
""
);
}}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
>
<option value="">Depo Seçiniz</option>
{mockWarehouses.map((warehouse) => (
<option
key={warehouse.id}
value={warehouse.id}
>
{warehouse.code}
</option>
))}
</select>
</div>
{/* Zone Selection */}
<div className="col-span-2">
<select
value={material.zoneId}
onChange={(e) => {
handleMaterialInputChange(
material.id,
"zoneId",
e.target.value
);
handleMaterialInputChange(
material.id,
"locationId",
""
);
}}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
disabled={!material.warehouseId}
>
<option value="">Zone Seç</option>
{availableZones.map((zone) => (
<option key={zone.id} value={zone.id}>
{zone.zoneCode} - {zone.name}
</option>
))}
</select>
</div>
{/* Location Selection */}
<div className="col-span-2">
<select
value={material.locationId}
onChange={(e) =>
handleMaterialInputChange(
material.id,
"locationId",
e.target.value
)
}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
disabled={!material.warehouseId}
>
<option value="">Lokasyon Seç</option>
{availableLocations.map((location) => (
<option
key={location.id}
value={location.id}
>
{location.locationCode} - {location.name}
</option>
))}
</select>
</div>
{/* Quantity */}
<div className="col-span-1">
<input
type="number"
value={material.quantity}
onChange={(e) =>
handleMaterialInputChange(
material.id,
"quantity",
e.target.value
)
}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Miktar"
min="0"
step="0.01"
required
/>
</div>
{/* Unit Price */}
<div className="col-span-1">
<input
type="number"
value={material.unitPrice}
onChange={(e) =>
handleMaterialInputChange(
material.id,
"unitPrice",
e.target.value
)
}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Fiyat"
min="0"
step="0.01"
/>
</div>
{/* Lot Number */}
<div className="col-span-1">
<input
type="text"
value={material.lotNumber}
onChange={(e) =>
handleMaterialInputChange(
material.id,
"lotNumber",
e.target.value
)
}
className="w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Lot"
/>
</div>
{/* Actions */}
<div className="col-span-1 flex justify-center">
<button
onClick={() => removeMaterial(material.id)}
className="text-red-600 hover:text-red-800 p-1"
title="Malzemeyi Sil"
>
<FaTrash className="w-3 h-3" />
</button>
</div>
</div>
{/* Additional info row for desktop */}
{selectedMaterial && (
<div className="mt-1 text-xs text-gray-500 grid grid-cols-12 gap-2">
<div className="col-span-2">
Birim: {selectedMaterial.baseUnit?.code} |
Stok: {selectedMaterial.totalStock}
</div>
<div className="col-span-2">
{selectedWarehouse?.name}
</div>
<div className="col-span-2">
{
availableZones.find(
(z) => z.id === material.zoneId
)?.name
}
</div>
<div className="col-span-2">
{
availableLocations.find(
(l) => l.id === material.locationId
)?.name
}
</div>
<div className="col-span-1">
{selectedMaterial.baseUnit?.code}
</div>
<div className="col-span-1">
{selectedMaterial.costPrice}
</div>
<div className="col-span-2">
{material.quantity &&
material.unitPrice &&
`Toplam: ₺${(
parseFloat(material.quantity) *
parseFloat(material.unitPrice)
).toFixed(2)}`}
</div>
</div>
)}
</div>
);
})}
</div>
</div>
{/* Mobile/Tablet Card View */}
<div className="lg:hidden space-y-3 max-h-80 overflow-y-auto">
{materialsList.map((material) => {
const selectedMaterial = mockMaterials.find(
(m) => m.id === material.materialId
);
const selectedWarehouse = mockWarehouses.find(
(w) => w.id === material.warehouseId
);
const availableZones = selectedWarehouse?.zones || [];
const availableLocations =
selectedWarehouse?.locations?.filter(
(location) =>
!material.zoneId ||
location.zoneId === material.zoneId
) || [];
return (
<div
key={material.id}
className="border border-gray-200 rounded-lg p-3 bg-white"
>
<div className="flex items-center justify-between mb-3">
<span className="text-sm font-medium text-gray-900">
Malzeme #{materialsList.indexOf(material) + 1}
</span>
<button
onClick={() => removeMaterial(material.id)}
className="text-red-600 hover:text-red-800 p-1"
title="Malzemeyi Sil"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{/* Material Selection */}
<div className="sm:col-span-2">
<label className="block text-xs font-medium text-gray-700 mb-1">
Malzeme *
</label>
<select
value={material.materialId}
onChange={(e) =>
handleMaterialInputChange(
material.id,
"materialId",
e.target.value
)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
>
<option value="">Malzeme Seçiniz</option>
{mockMaterials.map((mat) => (
<option key={mat.id} value={mat.id}>
{mat.code} - {mat.name}
</option>
))}
</select>
{selectedMaterial && (
<div className="mt-1 text-xs text-gray-500">
Birim: {selectedMaterial.baseUnit?.code} |
Stok: {selectedMaterial.totalStock}
</div>
)}
</div>
{/* Warehouse Selection */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Depo *
</label>
<select
value={material.warehouseId}
onChange={(e) => {
handleMaterialInputChange(
material.id,
"warehouseId",
e.target.value
);
handleMaterialInputChange(
material.id,
"zoneId",
""
);
handleMaterialInputChange(
material.id,
"locationId",
""
);
}}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
>
<option value="">Depo Seçiniz</option>
{mockWarehouses.map((warehouse) => (
<option
key={warehouse.id}
value={warehouse.id}
>
{warehouse.code} - {warehouse.name}
</option>
))}
</select>
</div>
{/* Zone Selection */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Zone
</label>
<select
value={material.zoneId}
onChange={(e) => {
handleMaterialInputChange(
material.id,
"zoneId",
e.target.value
);
handleMaterialInputChange(
material.id,
"locationId",
""
);
}}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
disabled={!material.warehouseId}
>
<option value="">Zone Seç</option>
{availableZones.map((zone) => (
<option key={zone.id} value={zone.id}>
{zone.zoneCode} - {zone.name}
</option>
))}
</select>
</div>
{/* Location Selection */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Lokasyon *
</label>
<select
value={material.locationId}
onChange={(e) =>
handleMaterialInputChange(
material.id,
"locationId",
e.target.value
)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
required
disabled={!material.warehouseId}
>
<option value="">Lokasyon Seç</option>
{availableLocations.map((location) => (
<option key={location.id} value={location.id}>
{location.locationCode} - {location.name}
</option>
))}
</select>
</div>
{/* Quantity */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Miktar *
</label>
<input
type="number"
value={material.quantity}
onChange={(e) =>
handleMaterialInputChange(
material.id,
"quantity",
e.target.value
)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Miktar"
min="0"
step="0.01"
required
/>
{selectedMaterial && (
<div className="mt-1 text-xs text-gray-500">
Birim: {selectedMaterial.baseUnit?.code}
</div>
)}
</div>
{/* Unit Price */}
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Birim Fiyat
</label>
<input
type="number"
value={material.unitPrice}
onChange={(e) =>
handleMaterialInputChange(
material.id,
"unitPrice",
e.target.value
)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder={`${
selectedMaterial?.costPrice || 0
}`}
min="0"
step="0.01"
/>
</div>
{/* Lot Number */}
<div className="sm:col-span-2">
<label className="block text-xs font-medium text-gray-700 mb-1">
Lot Numarası
</label>
<input
type="text"
value={material.lotNumber}
onChange={(e) =>
handleMaterialInputChange(
material.id,
"lotNumber",
e.target.value
)
}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-transparent"
placeholder="Lot numarası"
/>
</div>
</div>
{/* Total calculation */}
{selectedMaterial && material.quantity && (
<div className="mt-3 pt-3 border-t border-gray-200">
<div className="text-sm font-medium text-gray-900">
Toplam Tutar:
{(
parseFloat(material.quantity || "0") *
parseFloat(
material.unitPrice ||
selectedMaterial.costPrice?.toString() ||
"0"
)
).toFixed(2)}
</div>
</div>
)}
</div>
);
})}
</div>
</div>
{/* Form Actions */}
<div className="flex justify-between items-center gap-2 pt-4 border-t border-gray-200">
<button
onClick={addMaterial}
className="bg-blue-600 text-white px-3 py-1.5 rounded-lg text-sm hover:bg-blue-700 flex items-center gap-1"
>
<FaPlus className="w-3 h-3" />
Malzeme Ekle
</button>
<div className="flex gap-2">
<button
onClick={() => {
setShowReceiptForm(false);
resetForm();
}}
className="px-4 py-1.5 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
>
İptal
</button>
<button
onClick={handleSubmit}
className="px-4 py-1.5 text-sm bg-blue-600 text-white hover:bg-blue-700 rounded-lg transition-colors flex items-center gap-2"
>
<FaSave className="w-4 h-4" />
{editingMovement ? "Güncelle" : "Kaydet"}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default WarehouseReceipt;