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, "IsEnabled": true,
"MultiTenancySide": 2 "MultiTenancySide": 2
}, },
{ {
"GroupName": "App.Saas", "GroupName": "App.Saas",
"Name": "App.BlogManagement.Category", "Name": "App.BlogManagement.Category",
@ -1111,6 +1112,301 @@
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 2 "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", "GroupName": "App.Saas",
"Name": "App.BlogManagement.Posts", "Name": "App.BlogManagement.Posts",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -733,7 +733,7 @@ const WarehouseDefinitions: React.FC = () => {
<FaMapMarkerAlt className="w-5 h-5 text-orange-600" /> <FaMapMarkerAlt className="w-5 h-5 text-orange-600" />
</div> </div>
<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> <p className="text-sm text-gray-500">{location.locationCode}</p>
</div> </div>
</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

View file

@ -1,23 +1,48 @@
import React, { useState, useEffect } from "react"; import React, { useState } from 'react'
import { import { Formik, Form, Field, FieldProps, getIn } from 'formik'
WmLocation, import * as Yup from 'yup'
LocationTypeEnum, import { WmLocation, LocationTypeEnum, WmWarehouse, WmZone } from '../../../../types/wm'
WmWarehouse, import { FaTimes, FaSave, FaMapMarkerAlt } from 'react-icons/fa'
WmZone, import { getLocationTypeText } from '../../../../utils/erp'
} from "../../../../types/wm";
import { FaTimes, FaSave, FaMapMarkerAlt } from "react-icons/fa"; import { Input, Checkbox, FormContainer, FormItem } from '@/components/ui'
import { getLocationTypeText } from "../../../../utils/erp"; import Select from '@/components/ui/Select'
import Button from '@/components/ui/Button'
interface LocationModalProps { interface LocationModalProps {
isOpen: boolean; isOpen: boolean
onClose: () => void; onClose: () => void
onSave: (location: Partial<WmLocation>) => void; onSave: (location: Partial<WmLocation>) => void
location?: WmLocation | null; location?: WmLocation | null
mode: "create" | "edit" | "view"; mode: 'create' | 'edit' | 'view'
warehouses: WmWarehouse[]; warehouses: WmWarehouse[]
zones: WmZone[]; zones: WmZone[]
} }
const validationSchema = Yup.object({
warehouseId: Yup.string().required('Depo seçimi zorunludur'),
zoneId: Yup.string().required('Bölge seçimi zorunludur'),
locationCode: Yup.string().required('Lokasyon kodu zorunludur'),
name: Yup.string().required('Lokasyon adı zorunludur'),
capacity: Yup.number()
.moreThan(0, "Kapasite 0'dan büyük olmalıdır")
.required('Kapasite zorunludur'),
currentStock: Yup.number().test(
'stock-vs-capacity',
'Mevcut stok kapasiteyi aşamaz',
function (value) {
return (value || 0) <= (this.parent.capacity || 0)
},
),
dimensions: Yup.object({
length: Yup.number().min(0).nullable(),
width: Yup.number().min(0).nullable(),
height: Yup.number().min(0).nullable(),
weight: Yup.number().min(0).nullable(),
unit: Yup.string().required(),
}),
})
const LocationModal: React.FC<LocationModalProps> = ({ const LocationModal: React.FC<LocationModalProps> = ({
isOpen, isOpen,
onClose, onClose,
@ -27,167 +52,42 @@ const LocationModal: React.FC<LocationModalProps> = ({
warehouses, warehouses,
zones, zones,
}) => { }) => {
const [formData, setFormData] = useState<Partial<WmLocation>>({ const [restrictionInput, setRestrictionInput] = useState('')
warehouseId: "",
zoneId: "", if (!isOpen) return null
locationCode: "",
name: "", const initialValues: Partial<WmLocation> = {
description: "", warehouseId: location?.warehouseId || '',
locationType: LocationTypeEnum.Shelf, zoneId: location?.zoneId || '', // ✅ boş string, null yerine
capacity: 0, locationCode: location?.locationCode || '',
currentStock: 0, name: location?.name || '',
description: location?.description || '',
locationType: location?.locationType || LocationTypeEnum.Shelf,
capacity: location?.capacity || 0,
currentStock: location?.currentStock || 0,
dimensions: { dimensions: {
length: 0, length: location?.dimensions?.length || 0,
width: 0, width: location?.dimensions?.width || 0,
height: 0, height: location?.dimensions?.height || 0,
weight: 0, weight: location?.dimensions?.weight || 0,
unit: "kg", unit: location?.dimensions?.unit || 'kg',
}, },
restrictions: [], restrictions: location?.restrictions || [],
isActive: true, isActive: location?.isActive ?? true,
});
const [errors, setErrors] = useState<Record<string, string>>({});
const [restrictionInput, setRestrictionInput] = useState("");
useEffect(() => {
if (location) {
setFormData(location);
} else {
setFormData({
warehouseId: "",
zoneId: "",
locationCode: "",
name: "",
description: "",
locationType: LocationTypeEnum.Shelf,
capacity: 0,
currentStock: 0,
dimensions: {
length: 0,
width: 0,
height: 0,
weight: 0,
unit: "kg",
},
restrictions: [],
isActive: true,
});
} }
setErrors({});
setRestrictionInput("");
}, [location, isOpen]);
const validateForm = () => {
const newErrors: Record<string, string> = {};
if (!formData.warehouseId?.trim()) {
newErrors.warehouseId = "Depo seçimi zorunludur";
}
if (!formData.locationCode?.trim()) {
newErrors.locationCode = "Lokasyon kodu zorunludur";
}
if (!formData.name?.trim()) {
newErrors.name = "Lokasyon adı zorunludur";
}
if (!formData.capacity || formData.capacity <= 0) {
newErrors.capacity = "Kapasite 0'dan büyük olmalıdır";
}
if (
formData.currentStock &&
formData.currentStock > (formData.capacity || 0)
) {
newErrors.currentStock = "Mevcut stok kapasiteyi aşamaz";
}
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 | string[]
) => {
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]: "" }));
}
};
const getModalTitle = () => { const getModalTitle = () => {
switch (mode) { switch (mode) {
case "create": case 'create':
return "Yeni Lokasyon Oluştur"; return 'Yeni Lokasyon Oluştur'
case "edit": case 'edit':
return "Lokasyon Düzenle"; return 'Lokasyon Düzenle'
case "view": case 'view':
return "Lokasyon Görüntüle"; return 'Lokasyon Görüntüle'
default: default:
return "Lokasyon"; return 'Lokasyon'
} }
};
const addRestriction = () => {
if (
restrictionInput.trim() &&
!formData.restrictions?.includes(restrictionInput.trim())
) {
const newRestrictions = [
...(formData.restrictions || []),
restrictionInput.trim(),
];
handleInputChange("restrictions", newRestrictions);
setRestrictionInput("");
} }
};
const removeRestriction = (index: number) => {
const newRestrictions =
formData.restrictions?.filter((_, i) => i !== index) || [];
handleInputChange("restrictions", newRestrictions);
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
e.preventDefault();
addRestriction();
}
};
if (!isOpen) return null;
const selectedWarehouse = warehouses.find(
(w) => w.id === formData.warehouseId
);
const selectedZone = zones.find((z) => z.id === formData.zoneId);
const availableZones = zones.filter(
(zone) => zone.warehouseId === formData.warehouseId
);
return ( return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"> <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
@ -198,319 +98,286 @@ const LocationModal: React.FC<LocationModalProps> = ({
<div className="p-2 bg-orange-100 rounded-lg"> <div className="p-2 bg-orange-100 rounded-lg">
<FaMapMarkerAlt className="w-5 h-5 text-orange-600" /> <FaMapMarkerAlt className="w-5 h-5 text-orange-600" />
</div> </div>
<h2 className="text-lg font-semibold text-gray-900"> <h2 className="text-lg font-semibold text-gray-900">{getModalTitle()}</h2>
{getModalTitle()}
</h2>
</div> </div>
<button <button onClick={onClose} className="text-gray-400 hover:text-gray-600">
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
<FaTimes className="w-5 h-5" /> <FaTimes className="w-5 h-5" />
</button> </button>
</div> </div>
{/* Form */} {/* Form */}
<form onSubmit={handleSubmit} className="p-4 space-y-4"> <Formik
{/* Warehouse and Zone Selection */} initialValues={initialValues}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> validationSchema={validationSchema}
<div> onSubmit={(values, helpers) => {
<label className="block text-sm font-medium text-gray-700 mb-1"> if (mode !== 'view') {
Depo * console.log('Submitting values:', values)
</label> onSave(values)
<select onClose()
value={formData.warehouseId || ""} }
onChange={(e) => { helpers.setSubmitting(false)
handleInputChange("warehouseId", e.target.value);
handleInputChange("zoneId", ""); // Reset zone when warehouse changes
}} }}
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> {({ errors, touched, isSubmitting, values, setFieldValue }) => {
{warehouses.map((warehouse) => ( const availableZones = zones.filter((z) => z.warehouseId === values.warehouseId)
<option key={warehouse.id} value={warehouse.id}> const selectedWarehouse = warehouses.find((w) => w.id === values.warehouseId)
{warehouse.name} ({warehouse.code}) const selectedZone = zones.find((z) => z.id === values.zoneId)
</option>
))}
</select>
{errors.warehouseId && (
<p className="text-red-500 text-sm mt-1">
{errors.warehouseId}
</p>
)}
</div>
<div> const addRestriction = () => {
<label className="block text-sm font-medium text-gray-700 mb-1"> if (
Bölge restrictionInput.trim() &&
</label> !values.restrictions?.includes(restrictionInput.trim())
<select ) {
value={formData.zoneId || ""} setFieldValue('restrictions', [
onChange={(e) => handleInputChange("zoneId", e.target.value)} ...(values.restrictions || []),
disabled={mode === "view" || !formData.warehouseId} restrictionInput.trim(),
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" ])
> setRestrictionInput('')
<option value="">Bölge seçin (opsiyonel)</option> }
{availableZones.map((zone) => ( }
<option key={zone.id} value={zone.id}>
{zone.name} ({zone.zoneCode})
</option>
))}
</select>
</div>
</div>
{/* Basic Information */} const removeRestriction = (index: number) => {
setFieldValue(
'restrictions',
values.restrictions?.filter((_, i) => i !== index) || [],
)
}
return (
<Form className="p-4 space-y-4">
<FormContainer size="sm">
{/* Warehouse and Zone */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <FormItem
<label className="block text-sm font-medium text-gray-700 mb-1"> label="Depo *"
Lokasyon Kodu * invalid={!!(errors.warehouseId && touched.warehouseId)}
</label> errorMessage={errors.warehouseId}
<input
type="text"
value={formData.locationCode || ""}
onChange={(e) =>
handleInputChange("locationCode", 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="A01-01-01"
/>
{errors.locationCode && (
<p className="text-red-500 text-sm mt-1">
{errors.locationCode}
</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Lokasyon 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="A Blok 1. Koridor 1. Raf"
/>
{errors.name && (
<p className="text-red-500 text-sm mt-1">{errors.name}</p>
)}
</div>
</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="Lokasyon açıklaması..."
/>
</div>
{/* Location Type and Capacity */}
<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">
Lokasyon Tipi
</label>
<select
value={formData.locationType || LocationTypeEnum.Shelf}
onChange={(e) =>
handleInputChange(
"locationType",
e.target.value as LocationTypeEnum
)
}
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(LocationTypeEnum).map((type) => ( <Field name="warehouseId">
<option key={type} value={type}> {({ field, form }: FieldProps<string>) => (
{getLocationTypeText(type)} <Select
</option> field={field}
))} form={form}
</select> isClearable
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={(opt) => {
form.setFieldValue('warehouseId', opt?.value || '')
form.setFieldValue('zoneId', '') // ✅ reset
form.setFieldTouched('zoneId', false) // ✅ touched reset
}}
/>
)}
</Field>
</FormItem>
<FormItem label="Bölge">
<Field name="zoneId">
{({ field, form }: FieldProps<string | null>) => (
<Select
field={field}
form={form}
isClearable
isDisabled={mode === 'view' || !values.warehouseId}
options={availableZones.map((z) => ({
value: z.id,
label: `${z.name} (${z.zoneCode})`,
}))}
value={
values.zoneId
? availableZones
.map((z) => ({
value: z.id,
label: `${z.name} (${z.zoneCode})`,
}))
.find((opt) => opt.value === values.zoneId) || null
: null // ✅ boş string / null durumunda null döndür
}
onChange={(opt) => form.setFieldValue('zoneId', opt?.value ?? '')}
/>
)}
</Field>
</FormItem>
</div> </div>
<div> {/* Location Code + Name */}
<label className="block text-sm font-medium text-gray-700 mb-1"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
Kapasite * <FormItem
</label> label="Lokasyon Kodu *"
<input invalid={!!(errors.locationCode && touched.locationCode)}
errorMessage={errors.locationCode}
>
<Field
name="locationCode"
placeholder="A01-01-01"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
<FormItem
label="Lokasyon Adı *"
invalid={!!(errors.name && touched.name)}
errorMessage={errors.name}
>
<Field
name="name"
placeholder="A Blok 1. Koridor 1. Raf"
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
</div>
{/* Description */}
<FormItem label="Açıklama">
<Field
name="description"
as="textarea"
rows={3}
placeholder="Lokasyon açıklaması..."
disabled={mode === 'view'}
component={Input}
/>
</FormItem>
{/* Location Type + Capacity + Current Stock */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<FormItem label="Lokasyon Tipi">
<Field name="locationType">
{({ field, form }: FieldProps<LocationTypeEnum>) => (
<Select
field={field}
form={form}
isClearable={false}
isDisabled={mode === 'view'}
options={Object.values(LocationTypeEnum).map((t) => ({
value: t,
label: getLocationTypeText(t),
}))}
value={Object.values(LocationTypeEnum)
.map((t) => ({
value: t,
label: getLocationTypeText(t),
}))
.find((opt) => opt.value === values.locationType)}
onChange={(opt) => form.setFieldValue(field.name, opt?.value)}
/>
)}
</Field>
</FormItem>
<FormItem
label="Kapasite *"
invalid={!!(errors.capacity && touched.capacity)}
errorMessage={errors.capacity}
>
<Field
name="capacity"
type="number" 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="100" placeholder="100"
min={1}
disabled={mode === 'view'}
component={Input}
/> />
{errors.capacity && ( </FormItem>
<p className="text-red-500 text-sm mt-1">{errors.capacity}</p>
)}
</div>
<div> <FormItem
<label className="block text-sm font-medium text-gray-700 mb-1"> label="Mevcut Stok"
Mevcut Stok invalid={!!(errors.currentStock && touched.currentStock)}
</label> errorMessage={errors.currentStock}
<input >
<Field
name="currentStock"
type="number" type="number"
value={formData.currentStock || ""}
onChange={(e) =>
handleInputChange(
"currentStock",
parseInt(e.target.value) || 0
)
}
disabled={mode === "view"}
min="0"
max={formData.capacity || 0}
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="0" placeholder="0"
min={0}
disabled={mode === 'view'}
component={Input}
/> />
{errors.currentStock && ( </FormItem>
<p className="text-red-500 text-sm mt-1">
{errors.currentStock}
</p>
)}
</div>
</div> </div>
{/* Dimensions */} {/* Dimensions */}
<div> <div>
<h3 className="text-base font-medium text-gray-900 mb-2"> <h3 className="text-base font-medium text-gray-900 mb-2">Boyutlar</h3>
Boyutlar
</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3"> <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<div> {['length', 'width', 'height', 'weight'].map((dim) => (
<label className="block text-sm font-medium text-gray-700 mb-1"> <FormItem
Uzunluk (m) key={dim}
</label> label={
<input dim === 'length'
type="number" ? 'Uzunluk (m)'
value={formData.dimensions?.length || ""} : dim === 'width'
onChange={(e) => ? 'Genişlik (m)'
handleInputChange( : dim === 'height'
"dimensions.length", ? 'Yükseklik (m)'
parseFloat(e.target.value) || 0 : `Maks. Ağırlık (${values.dimensions?.unit})`
}
invalid={
!!(
getIn(errors, `dimensions.${dim}`) &&
getIn(touched, `dimensions.${dim}`)
) )
} }
disabled={mode === "view"} errorMessage={getIn(errors, `dimensions.${dim}`)}
min="0" >
<Field
name={`dimensions.${dim}`}
type="number"
step="0.1" 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" min={0}
placeholder="2.5" disabled={mode === 'view'}
component={Input}
/> />
</div> </FormItem>
))}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Genişlik (m)
</label>
<input
type="number"
value={formData.dimensions?.width || ""}
onChange={(e) =>
handleInputChange(
"dimensions.width",
parseFloat(e.target.value) || 0
)
}
disabled={mode === "view"}
min="0"
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="1.2"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Yükseklik (m)
</label>
<input
type="number"
value={formData.dimensions?.height || ""}
onChange={(e) =>
handleInputChange(
"dimensions.height",
parseFloat(e.target.value) || 0
)
}
disabled={mode === "view"}
min="0"
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="3.0"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Maks. ırlık ({formData.dimensions?.unit || "kg"})
</label>
<input
type="number"
value={formData.dimensions?.weight || ""}
onChange={(e) =>
handleInputChange(
"dimensions.weight",
parseFloat(e.target.value) || 0
)
}
disabled={mode === "view"}
min="0"
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"
/>
</div>
</div> </div>
</div> </div>
{/* Restrictions */} {/* Restrictions */}
<div> <div>
<h3 className="text-base font-medium text-gray-900 mb-2"> <h3 className="text-base font-medium text-gray-900 mb-2">Kısıtlamalar</h3>
Kısıtlamalar {mode !== 'view' && (
</h3>
{mode !== "view" && (
<div className="flex gap-2 mb-3"> <div className="flex gap-2 mb-3">
<input <Input
type="text"
value={restrictionInput} value={restrictionInput}
onChange={(e) => setRestrictionInput(e.target.value)} onChange={(e) => setRestrictionInput(e.target.value)}
onKeyPress={handleKeyPress} onKeyDown={(e) => {
className="flex-1 px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" if (e.key === 'Enter') {
e.preventDefault()
addRestriction()
}
}}
placeholder="Kısıtlama ekle..." placeholder="Kısıtlama ekle..."
/> />
<button <Button
type="button" type="button"
variant="solid"
color="blue-600"
onClick={addRestriction} onClick={addRestriction}
className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700"
> >
Ekle Ekle
</button> </Button>
</div> </div>
)} )}
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2 mb-2">
{formData.restrictions?.map((restriction, index) => ( {values.restrictions?.map((restriction, index) => (
<span <span
key={index} key={`${restriction}-${index}`}
className="inline-flex items-center px-2.5 py-1 text-xs bg-yellow-100 text-yellow-800 rounded-full" className="inline-flex items-center px-2.5 py-1 text-xs bg-yellow-100 text-yellow-800 rounded-full"
> >
{restriction} {restriction}
{mode !== "view" && ( {mode !== 'view' && (
<button <button
type="button" type="button"
onClick={() => removeRestriction(index)} onClick={() => removeRestriction(index)}
@ -525,29 +392,21 @@ const LocationModal: React.FC<LocationModalProps> = ({
</div> </div>
{/* Status */} {/* Status */}
<div className="flex items-center"> <FormItem>
<input <Field
name="isActive"
type="checkbox" type="checkbox"
id="isActive" component={Checkbox}
checked={formData.isActive || false} disabled={mode === 'view'}
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 Aktif
</label> </Field>
</div> </FormItem>
{/* Info (in view mode) */} {/* Info (View Mode) */}
{mode === "view" && ( {mode === 'view' && (
<div className="bg-gray-50 rounded-lg p-3 space-y-2"> <div className="bg-gray-50 rounded-lg p-3 space-y-2">
<h4 className="text-sm font-medium text-gray-900"> <h4 className="text-sm font-medium text-gray-900">Lokasyon Bilgileri</h4>
Lokasyon Bilgileri
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600">
{selectedWarehouse && ( {selectedWarehouse && (
<div> <div>
@ -555,7 +414,7 @@ const LocationModal: React.FC<LocationModalProps> = ({
<strong>Depo:</strong> {selectedWarehouse.name} <strong>Depo:</strong> {selectedWarehouse.name}
</p> </p>
<p> <p>
<strong>Depo Kodu:</strong> {selectedWarehouse.code} <strong>Kodu:</strong> {selectedWarehouse.code}
</p> </p>
</div> </div>
)} )}
@ -565,21 +424,19 @@ const LocationModal: React.FC<LocationModalProps> = ({
<strong>Bölge:</strong> {selectedZone.name} <strong>Bölge:</strong> {selectedZone.name}
</p> </p>
<p> <p>
<strong>Bölge Kodu:</strong> {selectedZone.zoneCode} <strong>Kodu:</strong> {selectedZone.zoneCode}
</p> </p>
</div> </div>
)} )}
<div> <div>
<p> <p>
<strong>Kapasite Kullanımı:</strong>{" "} <strong>Kapasite Kullanımı:</strong> {values.currentStock} /{' '}
{formData.currentStock || 0} / {formData.capacity || 0} {values.capacity}
</p> </p>
<p> <p>
<strong>Doluluk Oranı:</strong> % <strong>Doluluk Oranı:</strong> %
{Math.round( {Math.round(
((formData.currentStock || 0) / ((values.currentStock || 0) / (values.capacity || 1)) * 100,
(formData.capacity || 1)) *
100
)} )}
</p> </p>
</div> </div>
@ -589,27 +446,34 @@ const LocationModal: React.FC<LocationModalProps> = ({
{/* Buttons */} {/* Buttons */}
<div className="flex justify-end gap-2 pt-4 border-t border-gray-200"> <div className="flex justify-end gap-2 pt-4 border-t border-gray-200">
<button <Button
type="button" type="button"
onClick={onClose} 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" variant="default"
disabled={isSubmitting}
> >
{mode === "view" ? "Kapat" : "İptal"} {mode === 'view' ? 'Kapat' : 'İptal'}
</button> </Button>
{mode !== "view" && ( {mode !== 'view' && (
<button <Button
type="submit" 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" variant="solid"
color="blue-600"
loading={isSubmitting}
icon={<FaSave className="w-4 h-4" />}
> >
<FaSave className="w-4 h-4" /> {mode === 'create' ? 'Oluştur' : 'Güncelle'}
{mode === "create" ? "Oluştur" : "Güncelle"} </Button>
</button>
)} )}
</div> </div>
</form> </FormContainer>
</Form>
)
}}
</Formik>
</div> </div>
</div> </div>
); )
}; }
export default LocationModal; export default LocationModal

View file

@ -1,16 +1,37 @@
import React, { useState, useEffect } from "react"; import React from 'react'
import { WmWarehouse, WarehouseTypeEnum } from "../../../../types/wm"; import { Formik, Form, Field, FieldProps, getIn } from 'formik'
import { FaTimes, FaSave, FaBuilding } from "react-icons/fa"; import * as Yup from 'yup'
import { getWarehouseTypeText } from "../../../../utils/erp"; 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 { interface WarehouseModalProps {
isOpen: boolean; isOpen: boolean
onClose: () => void; onClose: () => void
onSave: (warehouse: Partial<WmWarehouse>) => void; onSave: (warehouse: Partial<WmWarehouse>) => void
warehouse?: WmWarehouse | null; warehouse?: WmWarehouse | null
mode: "create" | "edit" | "view"; 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> = ({ const WarehouseModal: React.FC<WarehouseModalProps> = ({
isOpen, isOpen,
onClose, onClose,
@ -18,123 +39,38 @@ const WarehouseModal: React.FC<WarehouseModalProps> = ({
warehouse, warehouse,
mode, mode,
}) => { }) => {
const [formData, setFormData] = useState<Partial<WmWarehouse>>({ if (!isOpen) return null
code: "",
name: "", const initialValues: Partial<WmWarehouse> = {
description: "", code: warehouse?.code || '',
name: warehouse?.name || '',
description: warehouse?.description || '',
address: { address: {
street: "", street: warehouse?.address?.street || '',
city: "", city: warehouse?.address?.city || '',
state: "", state: warehouse?.address?.state || '',
postalCode: "", postalCode: warehouse?.address?.postalCode || '',
country: "Türkiye", country: warehouse?.address?.country || 'Türkiye',
}, },
warehouseType: WarehouseTypeEnum.RawMaterials, warehouseType: warehouse?.warehouseType || WarehouseTypeEnum.RawMaterials,
isMainWarehouse: false, isMainWarehouse: warehouse?.isMainWarehouse ?? false,
capacity: 0, capacity: warehouse?.capacity || 0,
currentUtilization: 0, currentUtilization: warehouse?.currentUtilization || 0,
isActive: true, isActive: warehouse?.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]: "" }));
}
};
const getModalTitle = () => { const getModalTitle = () => {
switch (mode) { switch (mode) {
case "create": case 'create':
return "Yeni Depo Oluştur"; return 'Yeni Depo Oluştur'
case "edit": case 'edit':
return "Depo Düzenle"; return 'Depo Düzenle'
case "view": case 'view':
return "Depo Görüntüle"; return 'Depo Görüntüle'
default: default:
return "Depo"; return 'Depo'
}
} }
};
if (!isOpen) return null;
return ( return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"> <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"> <div className="p-2 bg-blue-100 rounded-lg">
<FaBuilding className="w-5 h-5 text-blue-600" /> <FaBuilding className="w-5 h-5 text-blue-600" />
</div> </div>
<h2 className="text-lg font-semibold text-gray-900"> <h2 className="text-lg font-semibold text-gray-900">{getModalTitle()}</h2>
{getModalTitle()}
</h2>
</div> </div>
<button <button onClick={onClose} className="text-gray-400 hover:text-gray-600">
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
<FaTimes className="w-5 h-5" /> <FaTimes className="w-5 h-5" />
</button> </button>
</div> </div>
{/* Form */} {/* Form */}
<form onSubmit={handleSubmit} className="p-4 space-y-4"> <Formik
{/* Basic Information */} initialValues={initialValues}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> validationSchema={validationSchema}
<div> onSubmit={(values, helpers) => {
<label className="block text-sm font-medium text-gray-700 mb-1"> if (mode !== 'view') {
Depo Kodu * console.log('Submitting values:', values)
</label> onSave(values)
<input onClose()
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>
<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>
<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"} helpers.setSubmitting(false)
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) => ( {({ errors, touched, isSubmitting, values }) => (
<option key={type} value={type}> <Form className="p-4 space-y-4">
{getWarehouseTypeText(type)} <FormContainer size="sm">
</option> {/* Basic Info */}
))}
</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="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex items-center"> <FormItem
<input label="Depo Kodu *"
type="checkbox" invalid={!!(errors.code && touched.code)}
id="isMainWarehouse" errorMessage={errors.code}
checked={formData.isMainWarehouse || false} >
onChange={(e) => <Field
handleInputChange("isMainWarehouse", e.target.checked) name="code"
} placeholder="WH001"
disabled={mode === "view"} disabled={mode === 'view'}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" component={Input}
/> />
<label </FormItem>
htmlFor="isMainWarehouse"
className="ml-2 block text-sm text-gray-900" <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>
{/* Description */}
<FormItem label="Açıklama">
<Field
name="description"
as="textarea"
rows={3}
placeholder="Depo açıklaması..."
disabled={mode === 'view'}
component={Input}
/>
</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 Ana Depo
</label> </Field>
</div> </FormItem>
<div className="flex items-center"> <FormItem>
<input <Field
name="isActive"
type="checkbox" type="checkbox"
id="isActive" component={Checkbox}
checked={formData.isActive || false} disabled={mode === 'view'}
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 Aktif
</label> </Field>
</div> </FormItem>
</div> </div>
{/* Address Information */} {/* Address */}
<div> <div>
<h3 className="text-base font-medium text-gray-900 mb-2"> <h3 className="text-base font-medium text-gray-900 mb-2">Adres Bilgileri</h3>
Adres Bilgileri
</h3>
<div className="space-y-4"> <div className="space-y-4">
<div> <FormItem
<label className="block text-sm font-medium text-gray-700 mb-1"> label="Adres *"
Adres * invalid={
</label> !!(getIn(errors, 'address.street') && getIn(touched, 'address.street'))
<input
type="text"
value={formData.address?.street || ""}
onChange={(e) =>
handleInputChange("address.street", e.target.value)
} }
disabled={mode === "view"} errorMessage={getIn(errors, 'address.street')}
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" >
<Field
name="address.street"
placeholder="Cadde/Sokak ve Bina No" placeholder="Cadde/Sokak ve Bina No"
disabled={mode === 'view'}
component={Input}
/> />
{errors.street && ( </FormItem>
<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 className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div> <FormItem
<label className="block text-sm font-medium text-gray-700 mb-1"> label="Şehir *"
Şehir * invalid={
</label> !!(getIn(errors, 'address.city') && getIn(touched, 'address.city'))
<input
type="text"
value={formData.address?.city || ""}
onChange={(e) =>
handleInputChange("address.city", e.target.value)
} }
disabled={mode === "view"} errorMessage={getIn(errors, 'address.city')}
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" >
<Field
name="address.city"
placeholder="İstanbul" placeholder="İstanbul"
disabled={mode === 'view'}
component={Input}
/> />
{errors.city && ( </FormItem>
<p className="text-red-500 text-sm mt-1">{errors.city}</p>
)}
</div>
<div> <FormItem label="İlçe">
<label className="block text-sm font-medium text-gray-700 mb-1"> <Field
İl name="address.state"
</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" placeholder="İstanbul"
disabled={mode === 'view'}
component={Input}
/> />
</div> </FormItem>
<div> <FormItem label="Posta Kodu">
<label className="block text-sm font-medium text-gray-700 mb-1"> <Field
Posta Kodu name="address.postalCode"
</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" placeholder="34000"
disabled={mode === 'view'}
component={Input}
/> />
</div> </FormItem>
</div> </div>
<div> <FormItem label="Ülke">
<label className="block text-sm font-medium text-gray-700 mb-1"> <Field
Ülke name="address.country"
</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" placeholder="Türkiye"
disabled={mode === 'view'}
component={Input}
/> />
</div> </FormItem>
</div> </div>
</div> </div>
{/* Buttons */} {/* Buttons */}
<div className="flex justify-end gap-2 pt-4 border-t border-gray-200"> <div className="flex justify-end gap-2 pt-4 border-t border-gray-200">
<button <Button type="button" onClick={onClose} variant="default" disabled={isSubmitting}>
type="button" {mode === 'view' ? 'Kapat' : 'İptal'}
onClick={onClose} </Button>
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' && (
> <Button
{mode === "view" ? "Kapat" : "İptal"}
</button>
{mode !== "view" && (
<button
type="submit" 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" variant="solid"
color="blue-600"
loading={isSubmitting}
icon={<FaSave className="w-4 h-4" />}
> >
<FaSave className="w-4 h-4" /> {mode === 'create' ? 'Oluştur' : 'Güncelle'}
{mode === "create" ? "Oluştur" : "Güncelle"} </Button>
</button>
)} )}
</div> </div>
</form> </FormContainer>
</Form>
)}
</Formik>
</div> </div>
</div> </div>
); )
}; }
export default WarehouseModal; export default WarehouseModal

View file

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