Eksik permission, Formik
This commit is contained in:
parent
70b3469494
commit
c11781e2b9
12 changed files with 1439 additions and 1956 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"), {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,10 @@ export const abpConfigModel: AbpConfigModel = {
|
||||||
|
|
||||||
actions.setConfig(result.data)
|
actions.setConfig(result.data)
|
||||||
|
|
||||||
await actions.getMenu()
|
//Eğer login değilse
|
||||||
|
if (result.data.currentUser.isAuthenticated) {
|
||||||
|
await actions.getMenu()
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
texts: undefined,
|
texts: undefined,
|
||||||
setTexts: action((state, payload) => {
|
setTexts: action((state, payload) => {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>()
|
||||||
|
|
|
||||||
|
|
@ -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[] {
|
||||||
|
|
|
||||||
|
|
@ -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,188 +34,202 @@ const SerialForm: React.FC<SerialFormProps> = ({
|
||||||
initial = null,
|
initial = null,
|
||||||
mode = 'create',
|
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
|
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 (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<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="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">
|
<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">
|
<Formik
|
||||||
<div className="flex items-center justify-between p-2 border-b">
|
initialValues={initialValues}
|
||||||
<div>
|
validationSchema={validationSchema}
|
||||||
<h2 className="text-xl font-bold text-gray-900">
|
onSubmit={async (values, helpers) => {
|
||||||
{mode === 'create'
|
const newSerial: MmSerialNumber = {
|
||||||
? 'Yeni Seri Kaydı'
|
id: initial?.id || Date.now().toString(),
|
||||||
: mode === 'edit'
|
materialId: values.materialId,
|
||||||
? 'Seri Düzenle'
|
serialNumber: values.serialNumber,
|
||||||
: 'Seri Detayı'}
|
lotId: values.lotId || undefined,
|
||||||
</h2>
|
productionDate: values.productionDate ? new Date(values.productionDate) : new Date(),
|
||||||
<p className="text-sm text-gray-600">
|
warrantyExpiryDate: values.warrantyExpiryDate
|
||||||
{mode === 'create'
|
? new Date(values.warrantyExpiryDate)
|
||||||
? 'Seri numarası girin'
|
: undefined,
|
||||||
: mode === 'edit'
|
currentLocationId: values.currentLocationId || undefined,
|
||||||
? 'Mevcut seri bilgisini güncelleyin'
|
status: values.status,
|
||||||
: 'Seri bilgileri (sadece gösterim)'}
|
isActive: !!values.isActive,
|
||||||
</p>
|
}
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
await new Promise((r) => setTimeout(r, 300))
|
||||||
<button
|
if (mode === 'edit' && onUpdate) {
|
||||||
type="button"
|
onUpdate(newSerial)
|
||||||
onClick={onClose}
|
} else {
|
||||||
className="px-2.5 py-1 text-sm border rounded-md bg-white hover:bg-gray-50"
|
onSave(newSerial)
|
||||||
>
|
}
|
||||||
<FaTimes className="inline mr-1 h-3 w-3" /> Kapat
|
onClose()
|
||||||
</button>
|
helpers.setSubmitting(false)
|
||||||
{mode !== 'view' && (
|
}}
|
||||||
<button
|
>
|
||||||
type="submit"
|
{({ values, errors, touched, setFieldValue, isSubmitting }) => (
|
||||||
className="px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center"
|
<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
|
Aktif
|
||||||
</button>
|
</Field>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</Form>
|
||||||
</div>
|
)}
|
||||||
|
</Formik>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">Açı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
|
|
@ -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 || ""}
|
helpers.setSubmitting(false)
|
||||||
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"
|
{({ errors, touched, isSubmitting, values }) => (
|
||||||
placeholder="WH001"
|
<Form className="p-4 space-y-4">
|
||||||
/>
|
<FormContainer size="sm">
|
||||||
{errors.warehouseCode && (
|
{/* Basic Info */}
|
||||||
<p className="text-red-500 text-sm mt-1">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{errors.warehouseCode}
|
<FormItem
|
||||||
</p>
|
label="Depo Kodu *"
|
||||||
)}
|
invalid={!!(errors.code && touched.code)}
|
||||||
</div>
|
errorMessage={errors.code}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
name="code"
|
||||||
|
placeholder="WH001"
|
||||||
|
disabled={mode === 'view'}
|
||||||
|
component={Input}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
<div>
|
<FormItem
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
label="Depo Adı *"
|
||||||
Depo 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)}
|
placeholder="Ana Depo"
|
||||||
disabled={mode === "view" || mode === "edit"}
|
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"
|
component={Input}
|
||||||
placeholder="Ana Depo"
|
/>
|
||||||
/>
|
</FormItem>
|
||||||
{errors.name && (
|
</div>
|
||||||
<p className="text-red-500 text-sm mt-1">{errors.name}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
{/* Description */}
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<FormItem label="Açıklama">
|
||||||
Açıklama
|
<Field
|
||||||
</label>
|
name="description"
|
||||||
<textarea
|
as="textarea"
|
||||||
value={formData.description || ""}
|
rows={3}
|
||||||
onChange={(e) => handleInputChange("description", e.target.value)}
|
placeholder="Depo açıklaması..."
|
||||||
disabled={mode === "view"}
|
disabled={mode === 'view'}
|
||||||
rows={3}
|
component={Input}
|
||||||
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"
|
|
||||||
/>
|
/>
|
||||||
{errors.city && (
|
</FormItem>
|
||||||
<p className="text-red-500 text-sm mt-1">{errors.city}</p>
|
|
||||||
|
{/* 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>
|
||||||
|
</FormContainer>
|
||||||
<div>
|
</Form>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
)}
|
||||||
İl
|
</Formik>
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default WarehouseModal;
|
export default WarehouseModal
|
||||||
|
|
|
||||||
|
|
@ -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°C’den küçük olamaz')
|
||||||
|
.max(100, 'Sıcaklık 100°C’den büyük olamaz')
|
||||||
|
.nullable(),
|
||||||
|
humidity: Yup.number()
|
||||||
|
.min(0, 'Nem %0’dan küçük olamaz')
|
||||||
|
.max(100, 'Nem %100’den 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 || '',
|
||||||
|
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(() => {
|
const selectedWarehouse = warehouses.find((w) => w.id === initialValues.warehouseId)
|
||||||
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 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,232 +78,216 @@ 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">
|
||||||
</div>
|
{({ 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 */}
|
{/* 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)}
|
placeholder="Z001"
|
||||||
disabled={mode === "view" || mode === "edit"}
|
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"
|
component={Input}
|
||||||
placeholder="Z001"
|
/>
|
||||||
/>
|
</FormItem>
|
||||||
{errors.zoneCode && (
|
|
||||||
<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)}
|
placeholder="Ana Depolama"
|
||||||
disabled={mode === "view" || mode === "edit"}
|
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"
|
component={Input}
|
||||||
placeholder="Ana Depolama"
|
/>
|
||||||
/>
|
</FormItem>
|
||||||
{errors.name && (
|
</div>
|
||||||
<p className="text-red-500 text-sm mt-1">{errors.name}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
{/* Description */}
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<FormItem label="Açıklama">
|
||||||
Açıklama
|
<Field
|
||||||
</label>
|
name="description"
|
||||||
<textarea
|
as="textarea"
|
||||||
value={formData.description || ""}
|
rows={3}
|
||||||
onChange={(e) => handleInputChange("description", e.target.value)}
|
placeholder="Bölge açıklaması..."
|
||||||
disabled={mode === "view"}
|
disabled={mode === 'view'}
|
||||||
rows={3}
|
component={Input}
|
||||||
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ı..."
|
</FormItem>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 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
|
>
|
||||||
type="number"
|
<Field
|
||||||
value={formData.temperature || ""}
|
name="temperature"
|
||||||
onChange={(e) =>
|
type="number"
|
||||||
handleInputChange(
|
placeholder="20"
|
||||||
"temperature",
|
min={-50}
|
||||||
e.target.value ? parseFloat(e.target.value) : undefined
|
max={100}
|
||||||
)
|
step={0.1}
|
||||||
}
|
disabled={mode === 'view'}
|
||||||
disabled={mode === "view"}
|
component={Input}
|
||||||
min="-50"
|
/>
|
||||||
max="100"
|
</FormItem>
|
||||||
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>
|
|
||||||
|
|
||||||
<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
|
>
|
||||||
type="number"
|
<Field
|
||||||
value={formData.humidity || ""}
|
name="humidity"
|
||||||
onChange={(e) =>
|
type="number"
|
||||||
handleInputChange(
|
placeholder="45"
|
||||||
"humidity",
|
min={0}
|
||||||
e.target.value ? parseFloat(e.target.value) : undefined
|
max={100}
|
||||||
)
|
step={0.1}
|
||||||
}
|
disabled={mode === 'view'}
|
||||||
disabled={mode === "view"}
|
component={Input}
|
||||||
min="0"
|
/>
|
||||||
max="100"
|
</FormItem>
|
||||||
step="0.1"
|
</div>
|
||||||
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>
|
|
||||||
|
|
||||||
{/* Status */}
|
{/* Status */}
|
||||||
<div className="flex items-center">
|
<FormItem>
|
||||||
<input
|
<Field
|
||||||
type="checkbox"
|
name="isActive"
|
||||||
id="isActive"
|
type="checkbox"
|
||||||
checked={formData.isActive || false}
|
component={Checkbox}
|
||||||
onChange={(e) => handleInputChange("isActive", e.target.checked)}
|
disabled={mode === 'view'}
|
||||||
disabled={mode === "view"}
|
>
|
||||||
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
Aktif
|
||||||
/>
|
</Field>
|
||||||
<label
|
</FormItem>
|
||||||
htmlFor="isActive"
|
|
||||||
className="ml-2 block text-sm text-gray-900"
|
|
||||||
>
|
|
||||||
Aktif
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 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
|
<div className="text-sm text-gray-600">
|
||||||
</h4>
|
<p>
|
||||||
<div className="text-sm text-gray-600">
|
<strong>Depo:</strong> {selectedWarehouse.name}
|
||||||
<p>
|
</p>
|
||||||
<strong>Depo:</strong> {selectedWarehouse.name}
|
<p>
|
||||||
</p>
|
<strong>Kod:</strong> {selectedWarehouse.code}
|
||||||
<p>
|
</p>
|
||||||
<strong>Kod:</strong> {selectedWarehouse.code}
|
<p>
|
||||||
</p>
|
<strong>Adres:</strong> {selectedWarehouse.address?.city},{' '}
|
||||||
<p>
|
{selectedWarehouse.address?.state}
|
||||||
<strong>Adres:</strong> {selectedWarehouse.address?.city},{" "}
|
</p>
|
||||||
{selectedWarehouse.address?.state}
|
</div>
|
||||||
</p>
|
</div>
|
||||||
</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>
|
||||||
)}
|
)}
|
||||||
|
</Formik>
|
||||||
{/* 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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ZoneModal;
|
export default ZoneModal
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue