Hatalar düzeltildi.
This commit is contained in:
parent
8daf982119
commit
c84b760a6b
7 changed files with 13 additions and 971 deletions
|
|
@ -18078,7 +18078,7 @@ public class ListFormSeeder : IDataSeedContributor, ITransientDependency
|
|||
new EditingFormItemDto { Order = 1, DataField = "Code", ColSpan = 2, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
|
||||
new EditingFormItemDto { Order = 2, DataField = "Name", ColSpan = 2, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
|
||||
new EditingFormItemDto { Order = 3, DataField = "GroupName", ColSpan = 2, IsRequired = false, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton },
|
||||
new EditingFormItemDto { Order = 4, DataField = "CurrencyCode", ColSpan = 2, IsRequired = false, EditorType2 = EditorTypes.dxTextBox },
|
||||
new EditingFormItemDto { Order = 4, DataField = "CurrencyId", ColSpan = 2, IsRequired = false, EditorType2 = EditorTypes.dxSelectBox },
|
||||
new EditingFormItemDto { Order = 5, DataField = "PhoneCode", ColSpan = 2, IsRequired = true, EditorType2 = EditorTypes.dxNumberBox },
|
||||
new EditingFormItemDto { Order = 6, DataField = "TaxLabel", ColSpan = 2, IsRequired = false, EditorType2 = EditorTypes.dxTextBox },
|
||||
new EditingFormItemDto { Order = 7, DataField = "ZipRequired", ColSpan = 2, IsRequired = false, EditorType2 = EditorTypes.dxCheckBox },
|
||||
|
|
@ -18230,13 +18230,20 @@ public class ListFormSeeder : IDataSeedContributor, ITransientDependency
|
|||
ListFormCode = listFormCountry.ListFormCode,
|
||||
CultureName = LanguageCodes.En,
|
||||
SourceDbType = DbType.String,
|
||||
FieldName = "CurrencyCode",
|
||||
FieldName = "CurrencyId",
|
||||
Width = 100,
|
||||
ListOrderNo = 5,
|
||||
Visible = true,
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
AllowSearch = false,
|
||||
LookupJson = JsonSerializer.Serialize(new LookupDto
|
||||
{
|
||||
DataSourceType = UiLookupDataSourceTypeEnum.Query,
|
||||
DisplayExpr = "name",
|
||||
ValueExpr = "key",
|
||||
LookupQuery = LookupQueryValues.CurrencyValues,
|
||||
}),
|
||||
ColumnCustomizationJson = JsonSerializer.Serialize(new ColumnCustomizationDto
|
||||
{
|
||||
AllowReordering = true,
|
||||
|
|
|
|||
|
|
@ -503,20 +503,6 @@
|
|||
"App.Coordinator.Tests"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "admin.supplychain.materialTypes",
|
||||
"path": "/admin/supplychain/materials/types",
|
||||
"componentPath": "@/views/supplychain/components/MaterialTypes",
|
||||
"routeType": "protected",
|
||||
"authority": null
|
||||
},
|
||||
{
|
||||
"key": "admin.supplychain.materialGroups",
|
||||
"path": "/admin/supplychain/materials/groups",
|
||||
"componentPath": "@/views/supplychain/components/MaterialGroups",
|
||||
"routeType": "protected",
|
||||
"authority": null
|
||||
},
|
||||
{
|
||||
"key": "admin.supplychain.materials",
|
||||
"path": "/admin/supplychain/materials",
|
||||
|
|
|
|||
|
|
@ -34,16 +34,6 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
|||
const [tagInput, setTagInput] = useState('')
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
// Debug kategorileri
|
||||
useEffect(() => {
|
||||
console.log('Categories received:', categories)
|
||||
}, [categories])
|
||||
|
||||
// Debug form data değişimini takip et
|
||||
useEffect(() => {
|
||||
console.log('FormData changed:', formData)
|
||||
}, [formData])
|
||||
|
||||
useEffect(() => {
|
||||
if (template) {
|
||||
setFormData({
|
||||
|
|
|
|||
|
|
@ -8,20 +8,22 @@ import { runBranchSeed } from '@/services/branch.service'
|
|||
function BranchSeed({
|
||||
open,
|
||||
onDialogClose,
|
||||
id,
|
||||
name,
|
||||
}: {
|
||||
open: boolean
|
||||
onDialogClose: () => void
|
||||
id: string
|
||||
name: string
|
||||
}) {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [result, setResult] = useState<BranchSeedResultDto | null>(null)
|
||||
|
||||
const handleRunSeed = async () => {
|
||||
if (!name) return
|
||||
if (!id) return
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const data = await runBranchSeed(name)
|
||||
const data = await runBranchSeed(id)
|
||||
setResult(data)
|
||||
|
||||
if (data.success) {
|
||||
|
|
|
|||
|
|
@ -1,564 +0,0 @@
|
|||
import React, { useState, useMemo } from 'react'
|
||||
import {
|
||||
FaPlus,
|
||||
FaEdit,
|
||||
FaTrash,
|
||||
FaSearch,
|
||||
FaTh,
|
||||
FaList,
|
||||
FaSitemap,
|
||||
FaChevronDown,
|
||||
FaChevronRight,
|
||||
FaAngleRight,
|
||||
} from 'react-icons/fa'
|
||||
import { mockMaterialGroups, populateParentGroups } from '../../../mocks/mockMaterialGroups'
|
||||
import { MmMaterialGroup } from '../../../types/mm'
|
||||
import { Container } from '@/components/shared'
|
||||
|
||||
interface MaterialGroupNode extends MmMaterialGroup {
|
||||
children: MaterialGroupNode[]
|
||||
}
|
||||
|
||||
const MaterialGroups: React.FC = () => {
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [editingGroup, setEditingGroup] = useState<MmMaterialGroup | null>(null)
|
||||
const [viewMode, setViewMode] = useState<'list' | 'card' | 'tree'>('tree')
|
||||
|
||||
// Mock data - gerçek uygulamada API'den gelecek
|
||||
const [materialGroups, setMaterialGroups] = useState<MmMaterialGroup[]>(mockMaterialGroups)
|
||||
|
||||
const filteredGroups = materialGroups.filter(
|
||||
(group) =>
|
||||
group.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
group.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(group.description && group.description.toLowerCase().includes(searchTerm.toLowerCase())),
|
||||
)
|
||||
|
||||
const handleAdd = () => {
|
||||
setEditingGroup(null)
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
|
||||
const handleEdit = (group: MmMaterialGroup) => {
|
||||
setEditingGroup(group)
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
|
||||
const handleDelete = (group: MmMaterialGroup) => {
|
||||
if (window.confirm(`${group.name} grubunu silmek istediğinizden emin misiniz?`)) {
|
||||
setMaterialGroups((prev) => prev.filter((g) => g.id !== group.id))
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = (formData: Partial<MmMaterialGroup>) => {
|
||||
if (editingGroup) {
|
||||
// Mevcut grubu güncelle
|
||||
setMaterialGroups((prev) =>
|
||||
prev.map((g) => (g.id === editingGroup.id ? { ...g, ...formData } : g)),
|
||||
)
|
||||
} else {
|
||||
// Yeni grup ekle
|
||||
const newGroup: MmMaterialGroup = {
|
||||
id: Date.now().toString(),
|
||||
code: formData.code!,
|
||||
name: formData.name!,
|
||||
parentGroupId: formData.parentGroupId || undefined,
|
||||
description: formData.description,
|
||||
isActive: formData.isActive ?? true,
|
||||
}
|
||||
setMaterialGroups((prev) => [...prev, newGroup])
|
||||
}
|
||||
setIsModalOpen(false)
|
||||
}
|
||||
|
||||
// Ağaç yapısını oluşturmak için useMemo kullanıyoruz
|
||||
const groupTree = useMemo(() => {
|
||||
const buildTree = (groups: MmMaterialGroup[]): MaterialGroupNode[] => {
|
||||
const groupMap = new Map<string, MaterialGroupNode>()
|
||||
const roots: MaterialGroupNode[] = []
|
||||
|
||||
// Her grubu bir düğüme dönüştür ve haritaya ekle
|
||||
groups.forEach((group) => {
|
||||
groupMap.set(group.id, { ...group, children: [] })
|
||||
})
|
||||
|
||||
// Üst-alt ilişkilerini kur
|
||||
groups.forEach((group) => {
|
||||
if (group.parentGroupId && groupMap.has(group.parentGroupId)) {
|
||||
const parent = groupMap.get(group.parentGroupId)!
|
||||
parent.children.push(groupMap.get(group.id)!)
|
||||
} else {
|
||||
roots.push(groupMap.get(group.id)!)
|
||||
}
|
||||
})
|
||||
|
||||
return roots
|
||||
}
|
||||
return buildTree(filteredGroups)
|
||||
}, [filteredGroups])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
||||
{/* Title & Description */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">Malzeme Grupları</h2>
|
||||
<p className="text-gray-600">Malzeme gruplarını yönetin</p>
|
||||
</div>
|
||||
|
||||
{/* Header Actions */}
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-1.5">
|
||||
{/* View Mode Toggle */}
|
||||
<div className="flex bg-gray-100 rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === 'list'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
title="Liste Görünümü"
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('card')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === 'card'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
title="Kart Görünümü"
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('tree')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === 'tree'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
title="Ağaç Görünümü"
|
||||
>
|
||||
<FaSitemap className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search Box */}
|
||||
<div className="relative">
|
||||
<FaSearch
|
||||
size={16}
|
||||
className="absolute left-2.5 top-1/2 transform -translate-y-1/2 text-gray-400"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Malzeme grubu ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-8 pr-3 py-1 w-full sm:w-56 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Add New Group */}
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className="flex items-center px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus size={14} className="mr-2" />
|
||||
Yeni Grup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{viewMode === 'list' && (
|
||||
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Grup Kodu
|
||||
</th>
|
||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Grup Adı
|
||||
</th>
|
||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Üst Grup
|
||||
</th>
|
||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Açıklama
|
||||
</th>
|
||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Durum
|
||||
</th>
|
||||
<th className="px-3 py-1.5 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">
|
||||
{filteredGroups.map((group) => (
|
||||
<tr key={group.id} className="hover:bg-gray-50 text-xs">
|
||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||
<span className="text-sm font-mono font-medium text-gray-900">
|
||||
{group.code}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||
<div className="text-sm font-medium text-gray-900">{group.name}</div>
|
||||
</td>
|
||||
<td className="px-3 py-1.5">
|
||||
<div className="text-sm text-gray-900">
|
||||
{group.parentGroup ? group.parentGroup.name : '-'}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-1.5">
|
||||
<div className="text-sm text-gray-900">{group.description}</div>
|
||||
</td>
|
||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||
group.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{group.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-1.5 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<button
|
||||
onClick={() => handleEdit(group)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
<FaEdit className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(group)}
|
||||
className="text-red-600 hover:text-red-900"
|
||||
>
|
||||
<FaTrash className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{filteredGroups.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-500">Malzeme grubu bulunamadı</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{viewMode === 'card' && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2">
|
||||
{filteredGroups.map((group) => (
|
||||
<div
|
||||
key={group.id}
|
||||
className="bg-white shadow-sm rounded-lg p-2.5 hover:shadow-md transition-shadow"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<span className="text-xs font-mono font-medium text-blue-600 bg-blue-50 px-2 py-1 rounded">
|
||||
{group.code}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<button
|
||||
onClick={() => handleEdit(group)}
|
||||
className="text-blue-600 hover:text-blue-900 p-1"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="h-3 w-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(group)}
|
||||
className="text-red-600 hover:text-red-900 p-1"
|
||||
title="Sil"
|
||||
>
|
||||
<FaTrash className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="font-medium text-gray-900 mb-1.5 text-sm">{group.name}</h3>
|
||||
|
||||
{group.description && (
|
||||
<p className="text-sm text-gray-600 mb-3 line-clamp-2">{group.description}</p>
|
||||
)}
|
||||
|
||||
<h3 className="text-gray-900 mb-3 text-sm">
|
||||
{group.parentGroupId && (
|
||||
<span className="text-sm text-gray-600">
|
||||
<FaAngleRight className="inline text-gray-600 mb-1" />
|
||||
{group.parentGroup?.name}
|
||||
</span>
|
||||
)}
|
||||
</h3>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||
group.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{group.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{filteredGroups.length === 0 && (
|
||||
<div className="col-span-full text-center py-12">
|
||||
<p className="text-gray-500">Malzeme grubu bulunamadı</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{viewMode === 'tree' && (
|
||||
<div className="bg-white shadow-sm rounded-lg p-3">
|
||||
{groupTree.map((node) => (
|
||||
<GroupTreeNode
|
||||
key={node.id}
|
||||
node={node}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
))}
|
||||
{groupTree.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-500">Ağaç görünümü için uygun malzeme grubu bulunamadı.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal */}
|
||||
{isModalOpen && (
|
||||
<MaterialGroupModal
|
||||
group={editingGroup}
|
||||
onSave={handleSave}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
// Ağaç Düğümü Komponenti
|
||||
const GroupTreeNode: React.FC<{
|
||||
node: MaterialGroupNode
|
||||
level?: number
|
||||
onEdit: (group: MmMaterialGroup) => void
|
||||
onDelete: (group: MmMaterialGroup) => void
|
||||
}> = ({ node, level = 0, onEdit, onDelete }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(level < 1) // İlk seviyeyi açık başlat
|
||||
const hasChildren = node.children && node.children.length > 0
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* Bağlantı çizgisi */}
|
||||
{level > 0 && (
|
||||
<span
|
||||
className="absolute border-l border-gray-300"
|
||||
style={{ left: `${level * 1.25 - 0.7}rem`, top: 0, height: '100%' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="flex items-center space-x-2 py-1.5 rounded-md hover:bg-gray-50"
|
||||
style={{ paddingLeft: `${level * 1.25}rem` }}
|
||||
>
|
||||
{/* Girinti ve bağlantı çizgisi */}
|
||||
{level > 0 && (
|
||||
<span
|
||||
className="absolute border-t border-gray-300 w-3"
|
||||
style={{ left: `${level * 1.25 - 0.7}rem`, top: '1.1rem' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Genişletme ikonu */}
|
||||
<div className="w-5 flex-shrink-0 text-center">
|
||||
{hasChildren && (
|
||||
<button
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="p-1 text-gray-500 hover:text-gray-800 rounded-full hover:bg-gray-200"
|
||||
>
|
||||
{isExpanded ? <FaChevronDown size={10} /> : <FaChevronRight size={10} />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Grup Bilgisi */}
|
||||
<div className="flex-grow flex items-center space-x-3">
|
||||
<span className="font-mono text-xs bg-blue-50 text-blue-700 px-2 py-0.5 rounded-md">
|
||||
{node.code}
|
||||
</span>
|
||||
<span className="text-sm font-medium text-gray-800">{node.name}</span>
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||
node.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{node.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* İşlemler */}
|
||||
<div className="flex items-center space-x-2 pr-2">
|
||||
<button
|
||||
onClick={() => onEdit(node)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onDelete(node)}
|
||||
className="text-red-600 hover:text-red-900"
|
||||
title="Sil"
|
||||
>
|
||||
<FaTrash className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{isExpanded && hasChildren && (
|
||||
<div>
|
||||
{node.children.map((child) => (
|
||||
<GroupTreeNode
|
||||
key={child.id}
|
||||
node={child}
|
||||
level={level + 1}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface MaterialGroupModalProps {
|
||||
group?: MmMaterialGroup | null
|
||||
onSave: (data: Partial<MmMaterialGroup>) => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const MaterialGroupModal: React.FC<MaterialGroupModalProps> = ({ group, onSave, onClose }) => {
|
||||
const [formData, setFormData] = useState({
|
||||
code: group?.code || '',
|
||||
name: group?.name || '',
|
||||
parentGroupId: group?.parentGroupId || '',
|
||||
description: group?.description || '',
|
||||
isActive: group?.isActive ?? true,
|
||||
})
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
onSave(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center p-4 z-50">
|
||||
<div className="bg-white rounded-lg w-full max-w-xs">
|
||||
<div className="px-3 py-2 border-b border-gray-200">
|
||||
<h3 className="text-sm font-medium text-gray-900">
|
||||
{group ? 'Malzeme Grubunu Düzenle' : 'Yeni Malzeme Grubu'}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="p-3 space-y-2">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Grup Kodu</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.code}
|
||||
onChange={(e) => setFormData({ ...formData, code: e.target.value.toUpperCase() })}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Grup Adı</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Üst Grup</label>
|
||||
<select
|
||||
value={formData.parentGroupId}
|
||||
onChange={(e) => setFormData({ ...formData, parentGroupId: e.target.value })}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Grup Seçiniz...</option>
|
||||
{populateParentGroups(mockMaterialGroups)
|
||||
.filter((g) => g.id !== group?.id) // Kendisini üst grup olarak seçemesin
|
||||
.map((g) => (
|
||||
<option key={g.id} value={g.id}>
|
||||
{g.name} ({g.code})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Açıklama</label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="isActive"
|
||||
checked={formData.isActive}
|
||||
onChange={(e) => setFormData({ ...formData, isActive: e.target.checked })}
|
||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label htmlFor="isActive" className="ml-2 text-sm text-gray-700">
|
||||
Aktif
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-2.5 py-1 text-sm text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200"
|
||||
>
|
||||
İptal
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-2.5 py-1 text-sm text-white bg-blue-600 rounded-md hover:bg-blue-700"
|
||||
>
|
||||
Kaydet
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialGroups
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
import { MaterialTypeEnum, MmMaterialType } from '@/types/mm'
|
||||
import { getMaterialTypeText } from '@/utils/erp'
|
||||
import { useState } from 'react'
|
||||
|
||||
interface MaterialTypeModalProps {
|
||||
type?: MmMaterialType | null
|
||||
onSave: (data: Partial<MmMaterialType>) => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const MaterialTypeModal: React.FC<MaterialTypeModalProps> = ({ type, onSave, onClose }) => {
|
||||
const [formData, setFormData] = useState({
|
||||
code: type?.code || MaterialTypeEnum.RawMaterial,
|
||||
name: type?.name || '',
|
||||
description: type?.description || '',
|
||||
isActive: type?.isActive ?? true,
|
||||
})
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
onSave(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||
<div className="bg-white rounded-lg w-full max-w-xs">
|
||||
<div className="px-3 py-2 border-b border-gray-200">
|
||||
<h3 className="text-sm font-medium text-gray-900">
|
||||
{type ? 'Malzeme Türünü Düzenle' : 'Yeni Malzeme Türü'}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="p-3 space-y-2">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Tür Kodu</label>
|
||||
<select
|
||||
value={formData.code}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
code: e.target.value as MaterialTypeEnum,
|
||||
})
|
||||
}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
required
|
||||
>
|
||||
{Object.values(MaterialTypeEnum).map((value) => (
|
||||
<option key={value} value={value}>
|
||||
{getMaterialTypeText(value)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Ad</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Açıklama</label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="isActive"
|
||||
checked={formData.isActive}
|
||||
onChange={(e) => setFormData({ ...formData, isActive: e.target.checked })}
|
||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label htmlFor="isActive" className="ml-2 text-sm text-gray-700">
|
||||
Aktif
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-2.5 py-1 text-sm text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200"
|
||||
>
|
||||
İptal
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-2.5 py-1 text-sm text-white bg-blue-600 rounded-md hover:bg-blue-700"
|
||||
>
|
||||
Kaydet
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialTypeModal
|
||||
|
|
@ -1,269 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
import { FaPlus, FaEdit, FaTrash, FaSearch, FaTh, FaList } from 'react-icons/fa'
|
||||
import { MmMaterialType, MaterialTypeEnum } from '../../../types/mm'
|
||||
import { mockMaterialTypes } from '../../../mocks/mockMaterialTypes'
|
||||
import { getMaterialTypeText } from '../../../utils/erp'
|
||||
import { Container } from '@/components/shared'
|
||||
import MaterialTypeModal from './MaterialTypeModal'
|
||||
|
||||
const MaterialTypes: React.FC = () => {
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [editingType, setEditingType] = useState<MmMaterialType | null>(null)
|
||||
const [viewMode, setViewMode] = useState<'list' | 'card'>('list')
|
||||
|
||||
// Mock data - gerçek uygulamada API'den gelecek
|
||||
const [materialTypes, setMaterialTypes] = useState<MmMaterialType[]>(mockMaterialTypes)
|
||||
|
||||
const filteredTypes = materialTypes.filter(
|
||||
(type) =>
|
||||
type.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
type.description?.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
)
|
||||
|
||||
const handleAdd = () => {
|
||||
setEditingType(null)
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
|
||||
const handleEdit = (type: MmMaterialType) => {
|
||||
setEditingType(type)
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
|
||||
const handleDelete = (type: MmMaterialType) => {
|
||||
if (window.confirm(`${type.name} türünü silmek istediğinizden emin misiniz?`)) {
|
||||
setMaterialTypes((prev) => prev.filter((t) => t.id !== type.id))
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = (formData: Partial<MmMaterialType>) => {
|
||||
if (editingType) {
|
||||
// Update existing type
|
||||
setMaterialTypes((prev) =>
|
||||
prev.map((t) => (t.id === editingType.id ? { ...t, ...formData } : t)),
|
||||
)
|
||||
} else {
|
||||
// Add new type
|
||||
const newType: MmMaterialType = {
|
||||
id: Date.now().toString(),
|
||||
code: formData.code as MaterialTypeEnum,
|
||||
name: formData.name!,
|
||||
description: formData.description,
|
||||
isActive: formData.isActive ?? true,
|
||||
className: '',
|
||||
}
|
||||
setMaterialTypes((prev) => [...prev, newType])
|
||||
}
|
||||
setIsModalOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<div className="space-y-2">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
||||
{/* Title & Description */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">Malzeme Türleri</h2>
|
||||
<p className="text-gray-600">Malzeme türlerini yönetin</p>
|
||||
</div>
|
||||
|
||||
{/* Header Actions */}
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-1.5">
|
||||
{/* View Mode Toggle */}
|
||||
<div className="flex bg-gray-100 rounded-lg p-1">
|
||||
<button
|
||||
onClick={() => setViewMode('list')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === 'list'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
title="Liste Görünümü"
|
||||
>
|
||||
<FaList className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('card')}
|
||||
className={`p-1.5 rounded-md transition-colors ${
|
||||
viewMode === 'card'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
title="Kart Görünümü"
|
||||
>
|
||||
<FaTh className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search Box */}
|
||||
<div className="relative">
|
||||
<FaSearch
|
||||
size={16}
|
||||
className="absolute left-2.5 top-1/2 transform -translate-y-1/2 text-gray-400"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Malzeme türü ara..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-8 pr-3 py-1 w-full sm:w-56 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Add New Type */}
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className="flex items-center px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<FaPlus size={14} className="mr-2" />
|
||||
Yeni Tür
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Types Display */}
|
||||
{viewMode === 'list' ? (
|
||||
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Kod
|
||||
</th>
|
||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Ad
|
||||
</th>
|
||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Açıklama
|
||||
</th>
|
||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Durum
|
||||
</th>
|
||||
<th className="px-3 py-1.5 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">
|
||||
{filteredTypes.map((type) => (
|
||||
<tr key={type.id} className="hover:bg-gray-50 text-xs">
|
||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||
<span className="text-sm font-mono font-medium text-gray-900">
|
||||
{getMaterialTypeText(type.code)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||
<div className="text-sm font-medium text-gray-900">{type.name}</div>
|
||||
</td>
|
||||
<td className="px-3 py-1.5">
|
||||
<div className="text-sm text-gray-900">{type.description}</div>
|
||||
</td>
|
||||
<td className="px-3 py-1.5 whitespace-nowrap">
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||
type.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{type.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-1.5 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<button
|
||||
onClick={() => handleEdit(type)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
<FaEdit className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(type)}
|
||||
className="text-red-600 hover:text-red-900"
|
||||
>
|
||||
<FaTrash className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{filteredTypes.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-500">Malzeme türü bulunamadı</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2">
|
||||
{filteredTypes.map((type) => (
|
||||
<div
|
||||
key={type.id}
|
||||
className="bg-white shadow-sm rounded-lg p-2.5 hover:shadow-md transition-shadow"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<span className="text-xs font-mono font-medium text-blue-600 bg-blue-50 px-2 py-1 rounded">
|
||||
{getMaterialTypeText(type.code)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<button
|
||||
onClick={() => handleEdit(type)}
|
||||
className="text-blue-600 hover:text-blue-900 p-1"
|
||||
title="Düzenle"
|
||||
>
|
||||
<FaEdit className="h-3 w-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(type)}
|
||||
className="text-red-600 hover:text-red-900 p-1"
|
||||
title="Sil"
|
||||
>
|
||||
<FaTrash className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="font-medium text-gray-900 mb-1.5 text-sm">{type.name}</h3>
|
||||
|
||||
{type.description && (
|
||||
<p className="text-sm text-gray-600 mb-3 line-clamp-2">{type.description}</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full ${
|
||||
type.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`}
|
||||
>
|
||||
{type.isActive ? 'Aktif' : 'Pasif'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{filteredTypes.length === 0 && (
|
||||
<div className="col-span-full text-center py-12">
|
||||
<p className="text-gray-500">Malzeme türü bulunamadı</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal */}
|
||||
{isModalOpen && (
|
||||
<MaterialTypeModal
|
||||
type={editingType}
|
||||
onSave={handleSave}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialTypes
|
||||
Loading…
Reference in a new issue