Eksik permission, Formik

This commit is contained in:
Sedat ÖZTÜRK 2025-09-18 15:46:07 +03:00
parent 70b3469494
commit c11781e2b9
12 changed files with 1439 additions and 1956 deletions

View file

@ -1063,6 +1063,7 @@
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.BlogManagement.Category",
@ -1111,6 +1112,301 @@
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.About",
"ParentName": null,
"DisplayName": "App.About",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.About.Create",
"ParentName": "App.About",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.About.Delete",
"ParentName": "App.About",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.About.Export",
"ParentName": "App.About",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.About.Import",
"ParentName": "App.About",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.About.Update",
"ParentName": "App.About",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Services",
"ParentName": null,
"DisplayName": "App.Services",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Services.Create",
"ParentName": "App.Services",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Services.Delete",
"ParentName": "App.Services",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Services.Export",
"ParentName": "App.Services",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Services.Import",
"ParentName": "App.Services",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Services.Update",
"ParentName": "App.Services",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.Products",
"ParentName": null,
"DisplayName": "App.Orders.Products",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.Products.Create",
"ParentName": "App.Orders.Products",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.Products.Delete",
"ParentName": "App.Orders.Products",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.Products.Export",
"ParentName": "App.Orders.Products",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.Products.Import",
"ParentName": "App.Orders.Products",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.Products.Update",
"ParentName": "App.Orders.Products",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PaymentMethods",
"ParentName": null,
"DisplayName": "App.Orders.PaymentMethods",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PaymentMethods.Create",
"ParentName": "App.Orders.PaymentMethods",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PaymentMethods.Delete",
"ParentName": "App.Orders.PaymentMethods",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PaymentMethods.Export",
"ParentName": "App.Orders.PaymentMethods",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PaymentMethods.Import",
"ParentName": "App.Orders.PaymentMethods",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PaymentMethods.Update",
"ParentName": "App.Orders.PaymentMethods",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.InstallmentOptions",
"ParentName": null,
"DisplayName": "App.Orders.InstallmentOptions",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.InstallmentOptions.Create",
"ParentName": "App.Orders.InstallmentOptions",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.InstallmentOptions.Delete",
"ParentName": "App.Orders.InstallmentOptions",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.InstallmentOptions.Export",
"ParentName": "App.Orders.InstallmentOptions",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.InstallmentOptions.Import",
"ParentName": "App.Orders.InstallmentOptions",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.InstallmentOptions.Update",
"ParentName": "App.Orders.InstallmentOptions",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PurchaseOrders",
"ParentName": null,
"DisplayName": "App.Orders.PurchaseOrders",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PurchaseOrders.Create",
"ParentName": "App.Orders.PurchaseOrders",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PurchaseOrders.Delete",
"ParentName": "App.Orders.PurchaseOrders",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PurchaseOrders.Export",
"ParentName": "App.Orders.PurchaseOrders",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PurchaseOrders.Import",
"ParentName": "App.Orders.PurchaseOrders",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.Orders.PurchaseOrders.Update",
"ParentName": "App.Orders.PurchaseOrders",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 2
},
{
"GroupName": "App.Saas",
"Name": "App.BlogManagement.Posts",

View file

@ -82,7 +82,7 @@ define(['./workbox-a959eb95'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "/index.html",
"revision": "0.v5arcquq4lo"
"revision": "0.sjlnuata16g"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("/index.html"), {

View file

@ -53,7 +53,10 @@ export const abpConfigModel: AbpConfigModel = {
actions.setConfig(result.data)
await actions.getMenu()
//Eğer login değilse
if (result.data.currentUser.isAuthenticated) {
await actions.getMenu()
}
}),
texts: undefined,
setTexts: action((state, payload) => {

View file

@ -41,7 +41,7 @@ export interface WmLocation {
// Lokasyon
id: string
warehouseId: string
zoneId?: string
zoneId: string
locationCode: string
name: string
description?: string

View file

@ -1,14 +1,15 @@
import { MenuItem } from '@/@types/menu'
import isDisabled from '@/components/ui/DatePicker/tables/components/props/isDisabled'
import { getMenus, MenuService } from '@/services/menu.service'
import { useStoreActions } from '@/store/store'
import { useState, useEffect } from 'react'
import useAuth from './useAuth'
export const useMenuData = () => {
const [menuItems, setMenuItems] = useState<MenuItem[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const { getConfig } = useStoreActions((a) => a.abpConfig)
const { authenticated } = useAuth()
const buildHierarchy = (items: MenuItem[]): MenuItem[] => {
const itemMap = new Map<string, MenuItem>()

View file

@ -102,7 +102,7 @@ function RolesPermission({
setTimeout(async () => {
getConfig(true)
}, 2000)
}, 4000)
}
function getPermissionsWithGroupName(groups: PermissionGroupDto[]): PermissionWithGroupName[] {

View file

@ -1,10 +1,13 @@
import React from 'react'
import { useFormik } from 'formik'
import { Formik, Form, Field } from 'formik'
import * as Yup from 'yup'
import { FaSave, FaTimes } from 'react-icons/fa'
import { SerialStatusEnum, MmSerialNumber, MmMaterial } from '../../../types/mm'
import { getSerialStatusText } from '@/utils/erp'
import { Input, Checkbox, Button } from '@/components/ui'
import Select from '@/components/ui/Select'
export interface SerialFormProps {
isOpen: boolean
onClose: () => void
@ -31,188 +34,202 @@ const SerialForm: React.FC<SerialFormProps> = ({
initial = null,
mode = 'create',
}) => {
const formik = useFormik({
initialValues: {
materialId: '',
serialNumber: '',
lotId: lots.length ? lots[0].id : '',
productionDate: '',
warrantyExpiryDate: '',
currentLocationId: '',
status: SerialStatusEnum.Available,
isActive: true,
},
validationSchema,
onSubmit: async (values) => {
const newSerial: MmSerialNumber = {
id: (initial && initial.id) || Date.now().toString(),
materialId: values.materialId,
serialNumber: values.serialNumber,
lotId: values.lotId || undefined,
productionDate: values.productionDate ? new Date(values.productionDate) : new Date(),
warrantyExpiryDate: values.warrantyExpiryDate
? new Date(values.warrantyExpiryDate)
: undefined,
currentLocationId: values.currentLocationId || undefined,
status: values.status,
isActive: !!values.isActive,
}
// simulate API
await new Promise((r) => setTimeout(r, 300))
if (mode === 'edit' && onUpdate) {
onUpdate(newSerial)
} else {
onSave(newSerial)
}
onClose()
},
})
// sync initial values when editing/viewing
React.useEffect(() => {
if (initial) {
const src = initial
formik.setValues({
materialId: src.materialId || '',
serialNumber: src.serialNumber || '',
lotId: src.lotId || (lots.length ? lots[0].id : ''),
productionDate: src.productionDate
? new Date(src.productionDate).toISOString().slice(0, 10)
: '',
warrantyExpiryDate: src.warrantyExpiryDate
? new Date(src.warrantyExpiryDate).toISOString().slice(0, 10)
: '',
currentLocationId: src.currentLocationId || '',
status: src.status || SerialStatusEnum.Available,
isActive: !!src.isActive,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initial])
if (!isOpen) return null
const initialValues = {
materialId: initial?.materialId || '',
serialNumber: initial?.serialNumber || '',
lotId: initial?.lotId || '',
productionDate: initial?.productionDate
? new Date(initial.productionDate).toISOString().slice(0, 10)
: '',
warrantyExpiryDate: initial?.warrantyExpiryDate
? new Date(initial.warrantyExpiryDate).toISOString().slice(0, 10)
: '',
currentLocationId: initial?.currentLocationId || '',
status: initial?.status || SerialStatusEnum.Available,
isActive: initial?.isActive ?? true,
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="absolute inset-0 bg-black opacity-40" onClick={onClose} />
<div className="relative bg-white rounded-lg shadow-lg w-full max-w-2xl mx-4">
<form onSubmit={formik.handleSubmit} className="space-y-2 p-3">
<div className="flex items-center justify-between p-2 border-b">
<div>
<h2 className="text-xl font-bold text-gray-900">
{mode === 'create'
? 'Yeni Seri Kaydı'
: mode === 'edit'
? 'Seri Düzenle'
: 'Seri Detayı'}
</h2>
<p className="text-sm text-gray-600">
{mode === 'create'
? 'Seri numarası girin'
: mode === 'edit'
? 'Mevcut seri bilgisini güncelleyin'
: 'Seri bilgileri (sadece gösterim)'}
</p>
</div>
<div className="flex items-center space-x-2">
<button
type="button"
onClick={onClose}
className="px-2.5 py-1 text-sm border rounded-md bg-white hover:bg-gray-50"
>
<FaTimes className="inline mr-1 h-3 w-3" /> Kapat
</button>
{mode !== 'view' && (
<button
type="submit"
className="px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center"
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={async (values, helpers) => {
const newSerial: MmSerialNumber = {
id: initial?.id || Date.now().toString(),
materialId: values.materialId,
serialNumber: values.serialNumber,
lotId: values.lotId || undefined,
productionDate: values.productionDate ? new Date(values.productionDate) : new Date(),
warrantyExpiryDate: values.warrantyExpiryDate
? new Date(values.warrantyExpiryDate)
: undefined,
currentLocationId: values.currentLocationId || undefined,
status: values.status,
isActive: !!values.isActive,
}
await new Promise((r) => setTimeout(r, 300))
if (mode === 'edit' && onUpdate) {
onUpdate(newSerial)
} else {
onSave(newSerial)
}
onClose()
helpers.setSubmitting(false)
}}
>
{({ values, errors, touched, setFieldValue, isSubmitting }) => (
<Form className="space-y-2 p-3">
{/* Header */}
<div className="flex items-center justify-between p-2 border-b">
<div>
<h2 className="text-lg font-bold text-gray-900">
{mode === 'create'
? 'Yeni Seri Kaydı'
: mode === 'edit'
? 'Seri Düzenle'
: 'Seri Detayı'}
</h2>
</div>
<div className="flex items-center gap-2">
<Button
size="sm"
type="button"
onClick={onClose}
variant="default"
className="flex items-center"
>
<FaTimes className="mr-1" />
<span>Kapat</span>
</Button>
{mode !== 'view' && (
<Button
size="sm"
type="submit"
variant="solid"
color="blue-600"
loading={isSubmitting}
icon={<FaSave className="w-4 h-4" />}
>
Kaydet
</Button>
)}
</div>
</div>
{/* Grid Form */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{/* Material */}
<div className="grid gap-1">
<label className="block text-sm text-gray-700">Malzeme *</label>
<Select
isDisabled={mode === 'view'}
options={materials.map((m) => ({
value: m.id,
label: `${m.code} - ${m.name}`,
}))}
value={materials
.map((m) => ({ value: m.id, label: `${m.code} - ${m.name}` }))
.find((opt) => opt.value === values.materialId)}
onChange={(opt) => setFieldValue('materialId', opt?.value || '')}
/>
{touched.materialId && errors.materialId && (
<p className="text-xs text-red-500 mt-1">{errors.materialId}</p>
)}
</div>
{/* Serial */}
<div className="grid gap-1">
<label className="block text-sm text-gray-700">Seri No *</label>
<Field
name="serialNumber"
placeholder="Seri numarası"
disabled={mode === 'view'}
component={Input}
size="sm"
/>
{touched.serialNumber && errors.serialNumber && (
<p className="text-xs text-red-500 mt-1">{errors.serialNumber}</p>
)}
</div>
{/* Lot */}
<div className="grid gap-1">
<label className="block text-sm text-gray-700">Lot</label>
<Select
size="sm"
isDisabled={mode === 'view'}
isClearable
options={lots.map((l) => ({ value: l.id, label: l.label }))}
value={lots
.map((l) => ({ value: l.id, label: l.label }))
.find((opt) => opt.value === values.lotId)}
onChange={(opt) => setFieldValue('lotId', opt?.value || '')}
/>
</div>
{/* Status */}
<div className="grid gap-1">
<label className="block text-sm text-gray-700">Durum</label>
<Select
size="sm"
isDisabled={mode === 'view'}
options={Object.values(SerialStatusEnum).map((s) => ({
value: s,
label: getSerialStatusText(s),
}))}
value={Object.values(SerialStatusEnum)
.map((s) => ({ value: s, label: getSerialStatusText(s) }))
.find((opt) => opt.value === values.status)}
onChange={(opt) => setFieldValue('status', opt?.value)}
/>
</div>
{/* Production Date */}
<div className="grid gap-1">
<label className="block text-sm text-gray-700">Üretim Tarihi</label>
<Field
name="productionDate"
type="date"
disabled={mode === 'view'}
component={Input}
size="sm"
/>
</div>
{/* Warranty Date */}
<div className="grid gap-1">
<label className="block text-sm text-gray-700">Garanti Bitiş</label>
<Field
name="warrantyExpiryDate"
type="date"
disabled={mode === 'view'}
component={Input}
size="sm"
/>
</div>
</div>
{/* Active Checkbox */}
<div className="pt-2">
<Field
name="isActive"
type="checkbox"
component={Checkbox}
disabled={mode === 'view'}
size="sm"
>
<FaSave className="inline mr-1 h-3 w-3" /> Kaydet
</button>
)}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 p-2 gap-x-3 gap-y-2">
<div>
<label className="block text-sm font-medium text-gray-700">Malzeme *</label>
<select
{...formik.getFieldProps('materialId')}
disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm"
>
<option value="">Seçiniz...</option>
{materials.map((m) => (
<option key={m.id} value={m.id}>
{m.code} - {m.name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Seri Numarası *</label>
<input
type="text"
{...formik.getFieldProps('serialNumber')}
disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Lot</label>
<select
{...formik.getFieldProps('lotId')}
disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm"
>
<option value="">Seçiniz...</option>
{lots.map((l) => (
<option key={l.id} value={l.id}>
{l.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Üretim Tarihi</label>
<input
type="date"
{...formik.getFieldProps('productionDate')}
disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Garanti Bitiş</label>
<input
type="date"
{...formik.getFieldProps('warrantyExpiryDate')}
disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Durum</label>
<select
{...formik.getFieldProps('status')}
disabled={mode === 'view'}
className="w-full border rounded-md px-2 py-1 text-sm"
>
{Object.values(SerialStatusEnum).map((status) => (
<option key={status} value={status}>
{getSerialStatusText(status)}
</option>
))}
</select>
</div>
</div>
</form>
Aktif
</Field>
</div>
</Form>
)}
</Formik>
</div>
</div>
)

View file

@ -733,7 +733,7 @@ const WarehouseDefinitions: React.FC = () => {
<FaMapMarkerAlt className="w-5 h-5 text-orange-600" />
</div>
<div>
<h3 className="font-medium text-gray-900">{location.name}</h3>
<div className="text-xs font-medium text-gray-900">{location.name}</div>
<p className="text-sm text-gray-500">{location.locationCode}</p>
</div>
</div>

View file

@ -1,515 +0,0 @@
import React, { useState, useEffect, useCallback } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { FaSave, FaTimes, FaBox, FaMapMarkerAlt, FaLayerGroup, FaBuilding } from 'react-icons/fa'
import LoadingSpinner from '../../../components/common/LoadingSpinner'
import { WmWarehouse, WarehouseTypeEnum } from '../../../types/wm'
import { SecurityLevelEnum } from '../../../types/mrp'
import { mockWarehouses } from '../../../mocks/mockWarehouses'
import { Container } from '@/components/shared'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { getWarehouseTypeText } from '@/utils/erp'
interface ValidationErrors {
[key: string]: string
}
const WarehouseForm: React.FC = () => {
const navigate = useNavigate()
const { id } = useParams<{ id: string }>()
const isEdit = Boolean(id)
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
const [errors, setErrors] = useState<ValidationErrors>({})
const [formData, setFormData] = useState<WmWarehouse>({
id: '',
code: '',
name: '',
description: '',
address: {
street: '',
city: '',
state: '',
postalCode: '',
country: 'Türkiye',
},
warehouseType: WarehouseTypeEnum.RawMaterials,
locations: [],
isMainWarehouse: false,
capacity: 0,
currentUtilization: 0,
zones: [],
isActive: true,
securityLevel: SecurityLevelEnum.Low,
temperatureControlled: false,
managerId: 0,
creationTime: new Date(),
lastModificationTime: new Date(),
})
const loadFormData = useCallback(async () => {
setLoading(true)
try {
if (isEdit && id) {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000))
const warehouse = mockWarehouses.find((w) => w.id === id)
if (warehouse) {
setFormData(warehouse)
}
}
} catch (error) {
console.error('Error loading form data:', error)
} finally {
setLoading(false)
}
}, [isEdit, id])
useEffect(() => {
loadFormData()
}, [loadFormData])
const validateForm = (): boolean => {
const newErrors: ValidationErrors = {}
if (!formData.code.trim()) {
newErrors.code = 'Depo kodu zorunludur'
}
if (!formData.name.trim()) {
newErrors.name = 'Depo adı zorunludur'
}
if (!formData.warehouseType) {
newErrors.warehouseType = 'Depo tipi seçilmelidir'
}
if (!formData.address?.street.trim()) {
newErrors['address.street'] = 'Adres zorunludur'
}
if (!formData.address?.city.trim()) {
newErrors['address.city'] = 'Şehir zorunludur'
}
if (!formData.address?.country.trim()) {
newErrors['address.country'] = 'Ülke zorunludur'
}
if (formData.capacity <= 0) {
newErrors.capacity = "Kapasite 0'dan büyük olmalıdır"
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleInputChange = (field: string, value: any) => {
setFormData((prev) => {
const updated = { ...prev }
const keys = field.split('.')
let current: any = updated
keys.forEach((key, index) => {
if (index === keys.length - 1) {
current[key] = value
} else {
current[key] = { ...current[key] }
current = current[key]
}
})
return updated
})
if (errors[field]) {
setErrors((prev) => ({ ...prev, [field]: '' }))
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validateForm()) {
return
}
setSaving(true)
try {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 2000))
console.log('Warehouse data:', {
...formData,
id: isEdit ? id : undefined,
})
// Show success message
alert(isEdit ? 'Depo başarıyla güncellendi!' : 'Depo başarıyla oluşturuldu!')
// Navigate back to list
navigate(ROUTES_ENUM.protected.warehouse.warehouses)
} catch (error) {
console.error('Error saving warehouse:', error)
alert('Bir hata oluştu. Lütfen tekrar deneyin.')
} finally {
setSaving(false)
}
}
const handleCancel = () => {
navigate(ROUTES_ENUM.protected.warehouse.warehouses)
}
if (loading) {
return <LoadingSpinner />
}
return (
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold text-gray-900">
{isEdit ? 'Depo Düzenle' : 'Yeni Depo'}
</h2>
<p className="text-sm text-gray-600 mt-1">
{isEdit ? 'Mevcut depo bilgilerini güncelleyin' : 'Yeni depo bilgilerini girin'}
</p>
</div>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-4">
{/* Basic Information */}
<div className="bg-white shadow rounded-lg">
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-medium text-gray-900 flex items-center">
<FaBuilding className="w-4 h-4 mr-2 text-gray-600" />
Genel Bilgiler
</h3>
</div>
<div className="p-4 space-y-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Depo Kodu *
</label>
<input
type="text"
value={formData.code}
onChange={(e) => handleInputChange('code', e.target.value)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.code
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="Örn: WH001"
/>
{errors.code && <p className="mt-1 text-sm text-red-600">{errors.code}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Depo Tipi *
</label>
<select
value={formData.warehouseType}
onChange={(e) => handleInputChange('warehouseType', e.target.value)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.warehouseType
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
>
<option value="">Tip seçin</option>
{Object.values(WarehouseTypeEnum).map((type) => (
<option key={type} value={type}>
{getWarehouseTypeText(type)}
</option>
))}
</select>
{errors.warehouseType && (
<p className="mt-1 text-sm text-red-600">{errors.warehouseType}</p>
)}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Depo Adı *</label>
<input
type="text"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.name
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="Depo adı"
/>
{errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">ıklama</label>
<textarea
value={formData.description}
onChange={(e) => handleInputChange('description', e.target.value)}
rows={3}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Depo açıklaması"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kapasite (m³) *
</label>
<input
type="number"
min="0"
step="0.01"
value={formData.capacity}
onChange={(e) => handleInputChange('capacity', parseFloat(e.target.value) || 0)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.capacity
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="0"
/>
{errors.capacity && (
<p className="mt-1 text-sm text-red-600">{errors.capacity}</p>
)}
</div>
<div className="flex items-center space-x-6 pt-6">
<div className="flex items-center">
<input
type="checkbox"
id="isMainWarehouse"
checked={formData.isMainWarehouse}
onChange={(e) => handleInputChange('isMainWarehouse', e.target.checked)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="isMainWarehouse" className="ml-2 block text-sm text-gray-900">
Ana Depo
</label>
</div>
<div className="flex items-center">
<input
type="checkbox"
id="isActive"
checked={formData.isActive}
onChange={(e) => handleInputChange('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 block text-sm text-gray-900">
Aktif
</label>
</div>
</div>
</div>
</div>
</div>
{/* Address Information */}
<div className="bg-white shadow rounded-lg">
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-medium text-gray-900 flex items-center">
<FaMapMarkerAlt className="w-4 h-4 mr-2 text-gray-600" />
Adres Bilgileri
</h3>
</div>
<div className="p-4 space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Adres *</label>
<input
type="text"
value={formData.address?.street}
onChange={(e) => handleInputChange('address.street', e.target.value)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors['address.street']
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="Sokak, cadde, mahalle"
/>
{errors['address.street'] && (
<p className="mt-1 text-sm text-red-600">{errors['address.street']}</p>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Şehir *</label>
<input
type="text"
value={formData.address?.city}
onChange={(e) => handleInputChange('address.city', e.target.value)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors['address.city']
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="Şehir"
/>
{errors['address.city'] && (
<p className="mt-1 text-sm text-red-600">{errors['address.city']}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">İl/Bölge</label>
<input
type="text"
value={formData.address?.state}
onChange={(e) => handleInputChange('address.state', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="İl/Bölge"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Posta Kodu</label>
<input
type="text"
value={formData.address?.postalCode}
onChange={(e) => handleInputChange('address.postalCode', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="34000"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Ülke *</label>
<input
type="text"
value={formData.address?.country}
onChange={(e) => handleInputChange('address.country', e.target.value)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors['address.country']
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="Ülke"
/>
{errors['address.country'] && (
<p className="mt-1 text-sm text-red-600">{errors['address.country']}</p>
)}
</div>
</div>
</div>
</div>
{/* Capacity Information */}
<div className="bg-white shadow rounded-lg">
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-medium text-gray-900 flex items-center">
<FaBox className="w-4 h-4 mr-2 text-gray-600" />
Kapasite Bilgileri
</h3>
</div>
<div className="p-4">
<div className="bg-gray-50 rounded-lg p-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
<div>
<span className="font-medium text-gray-700">Toplam Kapasite:</span>
<div className="text-lg font-semibold text-blue-600">
{formData.capacity.toLocaleString()} m³
</div>
</div>
<div>
<span className="font-medium text-gray-700">Mevcut Kullanım:</span>
<div className="text-lg font-semibold text-green-600">0 m³ (0%)</div>
</div>
<div>
<span className="font-medium text-gray-700">Müsait Kapasite:</span>
<div className="text-lg font-semibold text-gray-600">
{formData.capacity.toLocaleString()} m³
</div>
</div>
</div>
<div className="mt-4">
<div className="w-full bg-gray-200 rounded-full h-2">
<div className="bg-blue-600 h-2 rounded-full" style={{ width: '0%' }} />
</div>
<p className="text-xs text-gray-600 mt-1">Kullanım oranı: 0%</p>
</div>
</div>
</div>
</div>
{/* Warehouse Type Information */}
{formData.warehouseType && (
<div className="bg-white shadow rounded-lg">
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-medium text-gray-900 flex items-center">
<FaLayerGroup className="w-5 h-5 mr-2" />
Depo Tipi Özellikleri
</h3>
</div>
<div className="px-6 py-4">
<div className="bg-blue-50 border-l-4 border-blue-400 p-4">
<div className="flex">
<div className="ml-3">
<p className="text-sm text-blue-700">
{formData.warehouseType === WarehouseTypeEnum.RawMaterials &&
'Hammadde depolama için özel koşullara sahip olacaktır.'}
{formData.warehouseType === WarehouseTypeEnum.FinishedGoods &&
'Mamul depolama ve sevkiyat operasyonları için optimize edilecektir.'}
{formData.warehouseType === WarehouseTypeEnum.SpareParts &&
'Yedek parça yönetimi için özel raflar ve kategorilendirme yapılacaktır.'}
{formData.warehouseType === WarehouseTypeEnum.Returns &&
'Konsinye stok yönetimi için ayrı izleme sistemi kullanılacaktır.'}
{formData.warehouseType === WarehouseTypeEnum.Quarantine &&
'Karantina prosedürleri ve onay mekanizmaları aktif olacaktır.'}
{formData.warehouseType === WarehouseTypeEnum.Transit &&
'Sıcaklık kontrollü depolama sistemi kurulacaktır.'}
{formData.warehouseType === WarehouseTypeEnum.WorkInProgress &&
'Tehlikeli madde depolama güvenlik protokolleri uygulanacaktır.'}
</p>
</div>
</div>
</div>
</div>
</div>
)}
{/* Form Actions */}
<div className="flex items-center justify-end space-x-2 bg-gray-50 px-4 py-3 rounded-b-lg">
<button
type="button"
onClick={handleCancel}
className="inline-flex items-center px-4 py-1.5 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<FaTimes className="w-4 h-4 mr-2" />
İptal
</button>
<button
type="submit"
disabled={saving}
className="inline-flex items-center px-4 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
Kaydediliyor...
</>
) : (
<>
<FaSave className="w-4 h-4 mr-2" />
{isEdit ? 'Güncelle' : 'Kaydet'}
</>
)}
</button>
</div>
</form>
</div>
</Container>
)
}
export default WarehouseForm

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,37 @@
import React, { useState, useEffect } from "react";
import { WmWarehouse, WarehouseTypeEnum } from "../../../../types/wm";
import { FaTimes, FaSave, FaBuilding } from "react-icons/fa";
import { getWarehouseTypeText } from "../../../../utils/erp";
import React from 'react'
import { Formik, Form, Field, FieldProps, getIn } from 'formik'
import * as Yup from 'yup'
import { WmWarehouse, WarehouseTypeEnum } from '../../../../types/wm'
import { FaTimes, FaSave, FaBuilding } from 'react-icons/fa'
import { getWarehouseTypeText } from '../../../../utils/erp'
import { Input, Checkbox, FormContainer, FormItem } from '@/components/ui'
import Select from '@/components/ui/Select'
import Button from '@/components/ui/Button'
interface WarehouseModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (warehouse: Partial<WmWarehouse>) => void;
warehouse?: WmWarehouse | null;
mode: "create" | "edit" | "view";
isOpen: boolean
onClose: () => void
onSave: (warehouse: Partial<WmWarehouse>) => void
warehouse?: WmWarehouse | null
mode: 'create' | 'edit' | 'view'
}
const validationSchema = Yup.object({
code: Yup.string().required('Depo kodu zorunludur'),
name: Yup.string().required('Depo adı zorunludur'),
capacity: Yup.number()
.moreThan(0, "Kapasite 0'dan büyük olmalıdır")
.required('Kapasite zorunludur'),
address: Yup.object({
street: Yup.string().required('Adres zorunludur'),
city: Yup.string().required('Şehir zorunludur'),
state: Yup.string().nullable(),
postalCode: Yup.string().nullable(),
country: Yup.string().required('Ülke zorunludur'),
}),
})
const WarehouseModal: React.FC<WarehouseModalProps> = ({
isOpen,
onClose,
@ -18,123 +39,38 @@ const WarehouseModal: React.FC<WarehouseModalProps> = ({
warehouse,
mode,
}) => {
const [formData, setFormData] = useState<Partial<WmWarehouse>>({
code: "",
name: "",
description: "",
if (!isOpen) return null
const initialValues: Partial<WmWarehouse> = {
code: warehouse?.code || '',
name: warehouse?.name || '',
description: warehouse?.description || '',
address: {
street: "",
city: "",
state: "",
postalCode: "",
country: "Türkiye",
street: warehouse?.address?.street || '',
city: warehouse?.address?.city || '',
state: warehouse?.address?.state || '',
postalCode: warehouse?.address?.postalCode || '',
country: warehouse?.address?.country || 'Türkiye',
},
warehouseType: WarehouseTypeEnum.RawMaterials,
isMainWarehouse: false,
capacity: 0,
currentUtilization: 0,
isActive: true,
});
const [errors, setErrors] = useState<Record<string, string>>({});
useEffect(() => {
if (warehouse) {
setFormData(warehouse);
} else {
setFormData({
code: "",
name: "",
description: "",
address: {
street: "",
city: "",
state: "",
postalCode: "",
country: "Türkiye",
},
warehouseType: WarehouseTypeEnum.RawMaterials,
isMainWarehouse: false,
capacity: 0,
currentUtilization: 0,
isActive: true,
});
}
setErrors({});
}, [warehouse, isOpen]);
const validateForm = () => {
const newErrors: Record<string, string> = {};
if (!formData.code?.trim()) {
newErrors.code = "Depo kodu zorunludur";
}
if (!formData.name?.trim()) {
newErrors.name = "Depo adı zorunludur";
}
if (!formData.address?.street?.trim()) {
newErrors.street = "Adres zorunludur";
}
if (!formData.address?.city?.trim()) {
newErrors.city = "Şehir zorunludur";
}
if (!formData.capacity || formData.capacity <= 0) {
newErrors.capacity = "Kapasite 0'dan büyük olmalıdır";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (mode === "view") return;
if (validateForm()) {
onSave(formData);
onClose();
}
};
const handleInputChange = (
field: string,
value: string | number | boolean
) => {
if (field.includes(".")) {
const [parent, child] = field.split(".");
setFormData((prev) => ({
...prev,
[parent]: {
...(prev[parent as keyof typeof prev] as object),
[child]: value,
},
}));
} else {
setFormData((prev) => ({
...prev,
[field]: value,
}));
}
// Clear error when user starts typing
if (errors[field]) {
setErrors((prev) => ({ ...prev, [field]: "" }));
}
};
warehouseType: warehouse?.warehouseType || WarehouseTypeEnum.RawMaterials,
isMainWarehouse: warehouse?.isMainWarehouse ?? false,
capacity: warehouse?.capacity || 0,
currentUtilization: warehouse?.currentUtilization || 0,
isActive: warehouse?.isActive ?? true,
}
const getModalTitle = () => {
switch (mode) {
case "create":
return "Yeni Depo Oluştur";
case "edit":
return "Depo Düzenle";
case "view":
return "Depo Görüntüle";
case 'create':
return 'Yeni Depo Oluştur'
case 'edit':
return 'Depo Düzenle'
case 'view':
return 'Depo Görüntüle'
default:
return "Depo";
return 'Depo'
}
};
if (!isOpen) return null;
}
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
@ -145,275 +81,226 @@ const WarehouseModal: React.FC<WarehouseModalProps> = ({
<div className="p-2 bg-blue-100 rounded-lg">
<FaBuilding className="w-5 h-5 text-blue-600" />
</div>
<h2 className="text-lg font-semibold text-gray-900">
{getModalTitle()}
</h2>
<h2 className="text-lg font-semibold text-gray-900">{getModalTitle()}</h2>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
<FaTimes className="w-5 h-5" />
</button>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="p-4 space-y-4">
{/* Basic Information */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Depo Kodu *
</label>
<input
type="text"
value={formData.code || ""}
onChange={(e) => handleInputChange("code", e.target.value)}
disabled={mode === "view"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="WH001"
/>
{errors.warehouseCode && (
<p className="text-red-500 text-sm mt-1">
{errors.warehouseCode}
</p>
)}
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values, helpers) => {
if (mode !== 'view') {
console.log('Submitting values:', values)
onSave(values)
onClose()
}
helpers.setSubmitting(false)
}}
>
{({ errors, touched, isSubmitting, values }) => (
<Form className="p-4 space-y-4">
<FormContainer size="sm">
{/* Basic Info */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormItem
label="Depo Kodu *"
invalid={!!(errors.code && touched.code)}
errorMessage={errors.code}
>
<Field
name="code"
placeholder="WH001"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Depo Adı *
</label>
<input
type="text"
value={formData.name || ""}
onChange={(e) => handleInputChange("name", e.target.value)}
disabled={mode === "view" || mode === "edit"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="Ana Depo"
/>
{errors.name && (
<p className="text-red-500 text-sm mt-1">{errors.name}</p>
)}
</div>
</div>
<FormItem
label="Depo Adı *"
invalid={!!(errors.name && touched.name)}
errorMessage={errors.name}
>
<Field
name="name"
placeholder="Ana Depo"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
ıklama
</label>
<textarea
value={formData.description || ""}
onChange={(e) => handleInputChange("description", e.target.value)}
disabled={mode === "view"}
rows={3}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="Depo açıklaması..."
/>
</div>
{/* Warehouse Type and Settings */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Depo Tipi
</label>
<select
value={formData.warehouseType || WarehouseTypeEnum.RawMaterials}
onChange={(e) =>
handleInputChange("warehouseType", e.target.value)
}
disabled={mode === "view"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
>
{Object.values(WarehouseTypeEnum).map((type) => (
<option key={type} value={type}>
{getWarehouseTypeText(type)}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kapasite *
</label>
<input
type="number"
value={formData.capacity || ""}
onChange={(e) =>
handleInputChange("capacity", parseInt(e.target.value) || 0)
}
disabled={mode === "view"}
min="1"
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="1000"
/>
{errors.capacity && (
<p className="text-red-500 text-sm mt-1">{errors.capacity}</p>
)}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex items-center">
<input
type="checkbox"
id="isMainWarehouse"
checked={formData.isMainWarehouse || false}
onChange={(e) =>
handleInputChange("isMainWarehouse", e.target.checked)
}
disabled={mode === "view"}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label
htmlFor="isMainWarehouse"
className="ml-2 block text-sm text-gray-900"
>
Ana Depo
</label>
</div>
<div className="flex items-center">
<input
type="checkbox"
id="isActive"
checked={formData.isActive || false}
onChange={(e) =>
handleInputChange("isActive", e.target.checked)
}
disabled={mode === "view"}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label
htmlFor="isActive"
className="ml-2 block text-sm text-gray-900"
>
Aktif
</label>
</div>
</div>
{/* Address Information */}
<div>
<h3 className="text-base font-medium text-gray-900 mb-2">
Adres Bilgileri
</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Adres *
</label>
<input
type="text"
value={formData.address?.street || ""}
onChange={(e) =>
handleInputChange("address.street", e.target.value)
}
disabled={mode === "view"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="Cadde/Sokak ve Bina No"
/>
{errors.street && (
<p className="text-red-500 text-sm mt-1">{errors.street}</p>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Şehir *
</label>
<input
type="text"
value={formData.address?.city || ""}
onChange={(e) =>
handleInputChange("address.city", e.target.value)
}
disabled={mode === "view"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="İstanbul"
{/* Description */}
<FormItem label="Açıklama">
<Field
name="description"
as="textarea"
rows={3}
placeholder="Depo açıklaması..."
disabled={mode === 'view'}
component={Input}
/>
{errors.city && (
<p className="text-red-500 text-sm mt-1">{errors.city}</p>
</FormItem>
{/* Warehouse Type + Capacity */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormItem label="Depo Tipi">
<Field name="warehouseType">
{({ field, form }: FieldProps<WarehouseTypeEnum>) => (
<Select
field={field}
form={form}
isClearable={false}
isDisabled={mode === 'view'}
options={Object.values(WarehouseTypeEnum).map((t) => ({
value: t,
label: getWarehouseTypeText(t),
}))}
value={Object.values(WarehouseTypeEnum)
.map((t) => ({
value: t,
label: getWarehouseTypeText(t),
}))
.find((opt) => opt.value === values.warehouseType)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
<FormItem
label="Kapasite *"
invalid={!!(errors.capacity && touched.capacity)}
errorMessage={errors.capacity}
>
<Field
name="capacity"
type="number"
placeholder="1000"
min={1}
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
</div>
{/* Checkboxes */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormItem>
<Field
name="isMainWarehouse"
type="checkbox"
component={Checkbox}
disabled={mode === 'view'}
>
Ana Depo
</Field>
</FormItem>
<FormItem>
<Field
name="isActive"
type="checkbox"
component={Checkbox}
disabled={mode === 'view'}
>
Aktif
</Field>
</FormItem>
</div>
{/* Address */}
<div>
<h3 className="text-base font-medium text-gray-900 mb-2">Adres Bilgileri</h3>
<div className="space-y-4">
<FormItem
label="Adres *"
invalid={
!!(getIn(errors, 'address.street') && getIn(touched, 'address.street'))
}
errorMessage={getIn(errors, 'address.street')}
>
<Field
name="address.street"
placeholder="Cadde/Sokak ve Bina No"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<FormItem
label="Şehir *"
invalid={
!!(getIn(errors, 'address.city') && getIn(touched, 'address.city'))
}
errorMessage={getIn(errors, 'address.city')}
>
<Field
name="address.city"
placeholder="İstanbul"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
<FormItem label="İlçe">
<Field
name="address.state"
placeholder="İstanbul"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
<FormItem label="Posta Kodu">
<Field
name="address.postalCode"
placeholder="34000"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
</div>
<FormItem label="Ülke">
<Field
name="address.country"
placeholder="Türkiye"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
</div>
</div>
{/* Buttons */}
<div className="flex justify-end gap-2 pt-4 border-t border-gray-200">
<Button type="button" onClick={onClose} variant="default" disabled={isSubmitting}>
{mode === 'view' ? 'Kapat' : 'İptal'}
</Button>
{mode !== 'view' && (
<Button
type="submit"
variant="solid"
color="blue-600"
loading={isSubmitting}
icon={<FaSave className="w-4 h-4" />}
>
{mode === 'create' ? 'Oluştur' : 'Güncelle'}
</Button>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
İl
</label>
<input
type="text"
value={formData.address?.state || ""}
onChange={(e) =>
handleInputChange("address.state", e.target.value)
}
disabled={mode === "view"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="İstanbul"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Posta Kodu
</label>
<input
type="text"
value={formData.address?.postalCode || ""}
onChange={(e) =>
handleInputChange("address.postalCode", e.target.value)
}
disabled={mode === "view"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="34000"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Ülke
</label>
<input
type="text"
value={formData.address?.country || ""}
onChange={(e) =>
handleInputChange("address.country", e.target.value)
}
disabled={mode === "view"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="Türkiye"
/>
</div>
</div>
</div>
{/* Buttons */}
<div className="flex justify-end gap-2 pt-4 border-t border-gray-200">
<button
type="button"
onClick={onClose}
className="px-4 py-1.5 text-sm text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
>
{mode === "view" ? "Kapat" : "İptal"}
</button>
{mode !== "view" && (
<button
type="submit"
className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center gap-2"
>
<FaSave className="w-4 h-4" />
{mode === "create" ? "Oluştur" : "Güncelle"}
</button>
)}
</div>
</form>
</FormContainer>
</Form>
)}
</Formik>
</div>
</div>
);
};
)
}
export default WarehouseModal;
export default WarehouseModal

View file

@ -1,17 +1,38 @@
import React, { useState, useEffect } from "react";
import { WmZone, ZoneTypeEnum, WmWarehouse } from "../../../../types/wm";
import { FaTimes, FaSave, FaLayerGroup } from "react-icons/fa";
import { getZoneTypeText } from "../../../../utils/erp";
import React from 'react'
import { Formik, Form, Field, FieldProps } from 'formik'
import * as Yup from 'yup'
import { WmZone, ZoneTypeEnum, WmWarehouse } from '../../../../types/wm'
import { FaTimes, FaSave, FaLayerGroup } from 'react-icons/fa'
import { getZoneTypeText } from '../../../../utils/erp'
// Senin UI bileşenlerin
import { Input, Checkbox, FormContainer, FormItem } from '@/components/ui'
import Select from '@/components/ui/Select'
import Button from '@/components/ui/Button'
interface ZoneModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (zone: Partial<WmZone>) => void;
zone?: WmZone | null;
mode: "create" | "edit" | "view";
warehouses: WmWarehouse[];
isOpen: boolean
onClose: () => void
onSave: (zone: Partial<WmZone>) => void
zone?: WmZone | null
mode: 'create' | 'edit' | 'view'
warehouses: WmWarehouse[]
}
const validationSchema = Yup.object({
warehouseId: Yup.string().required('Depo seçimi zorunludur'),
zoneCode: Yup.string().required('Bölge kodu zorunludur'),
name: Yup.string().required('Bölge adı zorunludur'),
temperature: Yup.number()
.min(-50, 'Sıcaklık -50°Cden küçük olamaz')
.max(100, 'Sıcaklık 100°Cden büyük olamaz')
.nullable(),
humidity: Yup.number()
.min(0, 'Nem %0dan küçük olamaz')
.max(100, 'Nem %100den büyük olamaz')
.nullable(),
})
const ZoneModal: React.FC<ZoneModalProps> = ({
isOpen,
onClose,
@ -20,108 +41,33 @@ const ZoneModal: React.FC<ZoneModalProps> = ({
mode,
warehouses,
}) => {
const [formData, setFormData] = useState<Partial<WmZone>>({
warehouseId: "",
zoneCode: "",
name: "",
description: "",
zoneType: ZoneTypeEnum.Storage,
temperature: undefined,
humidity: undefined,
isActive: true,
});
if (!isOpen) return null
const [errors, setErrors] = useState<Record<string, string>>({});
const initialValues: Partial<WmZone> = {
warehouseId: zone?.warehouseId || '',
zoneCode: zone?.zoneCode || '',
name: zone?.name || '',
description: zone?.description || '',
zoneType: zone?.zoneType || ZoneTypeEnum.Storage,
temperature: zone?.temperature,
humidity: zone?.humidity,
isActive: zone?.isActive ?? true,
}
useEffect(() => {
if (zone) {
setFormData(zone);
} else {
setFormData({
warehouseId: "",
zoneCode: "",
name: "",
description: "",
zoneType: ZoneTypeEnum.Storage,
temperature: undefined,
humidity: undefined,
isActive: true,
});
}
setErrors({});
}, [zone, isOpen]);
const validateForm = () => {
const newErrors: Record<string, string> = {};
if (!formData.warehouseId?.trim()) {
newErrors.warehouseId = "Depo seçimi zorunludur";
}
if (!formData.zoneCode?.trim()) {
newErrors.zoneCode = "Bölge kodu zorunludur";
}
if (!formData.name?.trim()) {
newErrors.name = "Bölge adı zorunludur";
}
if (
formData.temperature &&
(formData.temperature < -50 || formData.temperature > 100)
) {
newErrors.temperature = "Sıcaklık -50°C ile 100°C arasında olmalıdır";
}
if (
formData.humidity &&
(formData.humidity < 0 || formData.humidity > 100)
) {
newErrors.humidity = "Nem %0 ile %100 arasında olmalıdır";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (mode === "view") return;
if (validateForm()) {
onSave(formData);
onClose();
}
};
const handleInputChange = (
field: string,
value: string | number | boolean | undefined
) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
// Clear error when user starts typing
if (errors[field]) {
setErrors((prev) => ({ ...prev, [field]: "" }));
}
};
const selectedWarehouse = warehouses.find((w) => w.id === initialValues.warehouseId)
const getModalTitle = () => {
switch (mode) {
case "create":
return "Yeni Bölge Oluştur";
case "edit":
return "Bölge Düzenle";
case "view":
return "Bölge Görüntüle";
case 'create':
return 'Yeni Bölge Oluştur'
case 'edit':
return 'Bölge Düzenle'
case 'view':
return 'Bölge Görüntüle'
default:
return "Bölge";
return 'Bölge'
}
};
if (!isOpen) return null;
const selectedWarehouse = warehouses.find(
(w) => w.id === formData.warehouseId
);
}
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
@ -132,232 +78,216 @@ const ZoneModal: React.FC<ZoneModalProps> = ({
<div className="p-2 bg-purple-100 rounded-lg">
<FaLayerGroup className="w-5 h-5 text-purple-600" />
</div>
<h2 className="text-lg font-semibold text-gray-900">
{getModalTitle()}
</h2>
<h2 className="text-lg font-semibold text-gray-900">{getModalTitle()}</h2>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
<FaTimes className="w-5 h-5" />
</button>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="p-4 space-y-4">
{/* Warehouse Selection */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Depo *
</label>
<select
value={formData.warehouseId || ""}
onChange={(e) => handleInputChange("warehouseId", e.target.value)}
disabled={mode === "view" || mode === "edit"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
>
<option value="">Depo seçin</option>
{warehouses.map((warehouse) => (
<option key={warehouse.id} value={warehouse.id}>
{warehouse.name} ({warehouse.code})
</option>
))}
</select>
{errors.warehouseId && (
<p className="text-red-500 text-sm mt-1">{errors.warehouseId}</p>
)}
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values, helpers) => {
if (mode !== 'view') {
console.log('Submitting values:', values)
onSave(values)
onClose()
}
helpers.setSubmitting(false)
}}
>
{({ errors, touched, isSubmitting, values }) => (
<Form className="p-4 space-y-4">
<FormContainer size="sm">
{/* Warehouse */}
<FormItem
label="Depo *"
invalid={!!(errors.warehouseId && touched.warehouseId)}
errorMessage={errors.warehouseId}
>
<Field name="warehouseId">
{({ field, form }: FieldProps<string>) => (
<Select
field={field}
form={form}
isClearable={true}
isDisabled={mode === 'view'}
options={warehouses.map((w) => ({
value: w.id,
label: `${w.name} (${w.code})`,
}))}
value={warehouses
.map((w) => ({
value: w.id,
label: `${w.name} (${w.code})`,
}))
.find((opt) => opt.value === values.warehouseId)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
{/* Basic Information */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Bölge Kodu *
</label>
<input
type="text"
value={formData.zoneCode || ""}
onChange={(e) => handleInputChange("zoneCode", e.target.value)}
disabled={mode === "view" || mode === "edit"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="Z001"
/>
{errors.zoneCode && (
<p className="text-red-500 text-sm mt-1">{errors.zoneCode}</p>
)}
</div>
{/* Zone Code + Zone Name */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormItem
label="Bölge Kodu *"
invalid={!!(errors.zoneCode && touched.zoneCode)}
errorMessage={errors.zoneCode}
>
<Field
name="zoneCode"
placeholder="Z001"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Bölge Adı *
</label>
<input
type="text"
value={formData.name || ""}
onChange={(e) => handleInputChange("name", e.target.value)}
disabled={mode === "view" || mode === "edit"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="Ana Depolama"
/>
{errors.name && (
<p className="text-red-500 text-sm mt-1">{errors.name}</p>
)}
</div>
</div>
<FormItem
label="Bölge Adı *"
invalid={!!(errors.name && touched.name)}
errorMessage={errors.name}
>
<Field
name="name"
placeholder="Ana Depolama"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
ıklama
</label>
<textarea
value={formData.description || ""}
onChange={(e) => handleInputChange("description", e.target.value)}
disabled={mode === "view"}
rows={3}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="Bölge açıklaması..."
/>
</div>
{/* Description */}
<FormItem label="Açıklama">
<Field
name="description"
as="textarea"
rows={3}
placeholder="Bölge açıklaması..."
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
{/* Zone Type */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Bölge Tipi
</label>
<select
value={formData.zoneType || ZoneTypeEnum.Storage}
onChange={(e) =>
handleInputChange("zoneType", e.target.value as ZoneTypeEnum)
}
disabled={mode === "view"}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
>
{Object.values(ZoneTypeEnum).map((type) => (
<option key={type} value={type}>
{getZoneTypeText(type)}
</option>
))}
</select>
</div>
{/* Zone Type */}
<FormItem label="Bölge Tipi">
<Field name="zoneType">
{({ field, form }: FieldProps<ZoneTypeEnum>) => (
<Select
field={field}
form={form}
isClearable={false}
isDisabled={mode === 'view'}
options={Object.values(ZoneTypeEnum).map((t) => ({
value: t,
label: getZoneTypeText(t),
}))}
value={Object.values(ZoneTypeEnum)
.map((t) => ({
value: t,
label: getZoneTypeText(t),
}))
.find((opt) => opt.value === values.zoneType)}
onChange={(option) => form.setFieldValue(field.name, option?.value)}
/>
)}
</Field>
</FormItem>
{/* Environmental Conditions */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Sıcaklık (°C)
</label>
<input
type="number"
value={formData.temperature || ""}
onChange={(e) =>
handleInputChange(
"temperature",
e.target.value ? parseFloat(e.target.value) : undefined
)
}
disabled={mode === "view"}
min="-50"
max="100"
step="0.1"
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="20"
/>
{errors.temperature && (
<p className="text-red-500 text-sm mt-1">
{errors.temperature}
</p>
)}
</div>
{/* Temperature + Humidity */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormItem
label="Sıcaklık (°C)"
invalid={!!(errors.temperature && touched.temperature)}
errorMessage={errors.temperature}
>
<Field
name="temperature"
type="number"
placeholder="20"
min={-50}
max={100}
step={0.1}
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nem (%)
</label>
<input
type="number"
value={formData.humidity || ""}
onChange={(e) =>
handleInputChange(
"humidity",
e.target.value ? parseFloat(e.target.value) : undefined
)
}
disabled={mode === "view"}
min="0"
max="100"
step="0.1"
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
placeholder="45"
/>
{errors.humidity && (
<p className="text-red-500 text-sm mt-1">{errors.humidity}</p>
)}
</div>
</div>
<FormItem
label="Nem (%)"
invalid={!!(errors.humidity && touched.humidity)}
errorMessage={errors.humidity}
>
<Field
name="humidity"
type="number"
placeholder="45"
min={0}
max={100}
step={0.1}
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
</div>
{/* Status */}
<div className="flex items-center">
<input
type="checkbox"
id="isActive"
checked={formData.isActive || false}
onChange={(e) => handleInputChange("isActive", e.target.checked)}
disabled={mode === "view"}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label
htmlFor="isActive"
className="ml-2 block text-sm text-gray-900"
>
Aktif
</label>
</div>
{/* Status */}
<FormItem>
<Field
name="isActive"
type="checkbox"
component={Checkbox}
disabled={mode === 'view'}
>
Aktif
</Field>
</FormItem>
{/* Warehouse Info (in view mode) */}
{mode === "view" && selectedWarehouse && (
<div className="bg-gray-50 rounded-lg p-3">
<h4 className="text-sm font-medium text-gray-900 mb-2">
Depo Bilgileri
</h4>
<div className="text-sm text-gray-600">
<p>
<strong>Depo:</strong> {selectedWarehouse.name}
</p>
<p>
<strong>Kod:</strong> {selectedWarehouse.code}
</p>
<p>
<strong>Adres:</strong> {selectedWarehouse.address?.city},{" "}
{selectedWarehouse.address?.state}
</p>
</div>
</div>
{/* Warehouse Info (view mode) */}
{mode === 'view' && selectedWarehouse && (
<div className="bg-gray-50 rounded-lg p-3">
<h4 className="text-sm font-medium text-gray-900 mb-2">Depo Bilgileri</h4>
<div className="text-sm text-gray-600">
<p>
<strong>Depo:</strong> {selectedWarehouse.name}
</p>
<p>
<strong>Kod:</strong> {selectedWarehouse.code}
</p>
<p>
<strong>Adres:</strong> {selectedWarehouse.address?.city},{' '}
{selectedWarehouse.address?.state}
</p>
</div>
</div>
)}
{/* Buttons */}
<div className="flex justify-end gap-2 pt-4 border-t border-gray-200">
<Button type="button" onClick={onClose} variant="default" disabled={isSubmitting}>
{mode === 'view' ? 'Kapat' : 'İptal'}
</Button>
{mode !== 'view' && (
<Button
type="submit"
variant="solid"
color="blue-600"
loading={isSubmitting}
icon={<FaSave className="w-4 h-4" />}
>
{mode === 'create' ? 'Oluştur' : 'Güncelle'}
</Button>
)}
</div>
</FormContainer>
</Form>
)}
{/* Buttons */}
<div className="flex justify-end gap-2 pt-4 border-t border-gray-200">
<button
type="button"
onClick={onClose}
className="px-4 py-1.5 text-sm text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
>
{mode === "view" ? "Kapat" : "İptal"}
</button>
{mode !== "view" && (
<button
type="submit"
className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center gap-2"
>
<FaSave className="w-4 h-4" />
{mode === "create" ? "Oluştur" : "Güncelle"}
</button>
)}
</div>
</form>
</Formik>
</div>
</div>
);
};
)
}
export default ZoneModal;
export default ZoneModal