Repository: GeorgeKalash/ArgusFE
Pull request #1200: AR-3205
Author: login: imtithalhalalo
Created: 2025-05-16T12:42:00Z | Merged: 2025-05-22T13:46:12Z

new screen serials profile


Diff:
diff --git a/src/pages/iv-categories/forms/CategoryForm.js b/src/pages/iv-categories/forms/CategoryForm.js
index 9f6341a0f8..cadde5b71b 100644
--- a/src/pages/iv-categories/forms/CategoryForm.js
+++ b/src/pages/iv-categories/forms/CategoryForm.js
@@ -376,7 +376,7 @@ const CategoryForm = ({ labels, maxAccess, setStore, store }) => {
                 {(formik.values.trackBy === '1' || formik.values.trackBy === 1) && (
                   <Grid item xs={12}>
                     <ResourceComboBox
-                      endpointId={InventoryRepository.SerialNumber.qry}
+                      endpointId={InventoryRepository.SerialsProfile.qry}
                       name='spfId'
                       required={formik.values.trackBy === '1' || formik.values.trackBy === 1}
                       label={labels.spf}
diff --git a/src/pages/iv-items/forms/ItemProductionForm.js b/src/pages/iv-items/forms/ItemProductionForm.js
index 2cabc7583a..dcfd3375e8 100644
--- a/src/pages/iv-items/forms/ItemProductionForm.js
+++ b/src/pages/iv-items/forms/ItemProductionForm.js
@@ -136,7 +136,7 @@ export default function ItemProductionForm({ labels, editMode, maxAccess, store
             </Grid>
             <Grid item xs={12}>
               <ResourceComboBox
-                endpointId={InventoryRepository.SerialProfile.qry}
+                endpointId={InventoryRepository.SerialsProfile.qry}
                 values={formik.values}
                 name='spfId'
                 label={labels.sprofile}
diff --git a/src/pages/iv-serial-profiles/Forms/SerialProfileSequences.js b/src/pages/iv-serial-profiles/Forms/SerialProfileSequences.js
new file mode 100644
index 0000000000..2654f1bc5c
--- /dev/null
+++ b/src/pages/iv-serial-profiles/Forms/SerialProfileSequences.js
@@ -0,0 +1,206 @@
+import { useContext, useEffect } from 'react'
+import { DataGrid } from 'src/components/Shared/DataGrid'
+import FormShell from 'src/components/Shared/FormShell'
+import { RequestsContext } from 'src/providers/RequestsContext'
+import { ResourceIds } from 'src/resources/ResourceIds'
+import toast from 'react-hot-toast'
+import * as yup from 'yup'
+import { useForm } from 'src/hooks/form'
+import { ControlContext } from 'src/providers/ControlContext'
+import { InventoryRepository } from 'src/repositories/InventoryRepository'
+import { DataSets } from 'src/resources/DataSets'
+
+const SerialProfileSequences = ({ store, maxAccess, labels }) => {
+  const { recordId } = store
+  const { getRequest, postRequest } = useContext(RequestsContext)
+  const { platformLabels } = useContext(ControlContext)
+
+  const createRowValidation = term =>
+    yup.mixed().test(function (value) {
+      const { position, str, sectionType, size, oper } = this.parent
+      const isAnyFieldFilled = !!(position || str || sectionType || size || oper)
+
+      if (term === 'oper') {
+        if (sectionType != 1) {
+          return true
+        }
+
+        return !!value
+      }
+
+      if (term === 'position' && !!position) {
+        if (Number(position) > 20) {
+          return false
+        }
+
+        return !!value
+      }
+
+      return isAnyFieldFilled ? !!value : true
+    })
+
+  const components = yup.array().of(
+    yup.object({
+      position: createRowValidation('position'),
+      str: createRowValidation(),
+      sectionType: createRowValidation(),
+      size: createRowValidation(),
+      oper: createRowValidation('oper')
+    })
+  )
+
+  const { formik } = useForm({
+    validateOnChange: true,
+    maxAccess,
+    initialValues: {
+      spfId: recordId,
+      components: [
+        {
+          id: 1,
+          spfId: recordId,
+          position: null,
+          seqNo: 1
+        }
+      ]
+    },
+    validationSchema: yup.object({
+      components
+    }),
+    onSubmit: async values => {
+      const modifiedItems = values?.components.map((details, index) => {
+        return {
+          ...details,
+          id: index + 1,
+          seqNo: index + 1,
+          spfId: recordId
+        }
+      })
+
+      await postRequest({
+        extension: InventoryRepository.SerialsProfile.set2,
+        record: JSON.stringify({ spfId: recordId, components: modifiedItems })
+      }).then(() => {
+        fetchGridData()
+        toast.success(platformLabels.Edited)
+      })
+    }
+  })
+
+  const editMode = !!recordId
+
+  async function fetchGridData() {
+    const res = await getRequest({
+      extension: InventoryRepository.SerialsProfileSequences.qry,
+      parameters: `_spfId=${recordId}`
+    })
+
+    const updateItemsList =
+      res?.list?.length !== 0
+        ? await Promise.all(
+            res.list.map(async (item, index) => {
+              return {
+                ...item,
+                id: index + 1,
+                position: item.position === 0 ? null : item.position,
+                oper: item.oper || 0
+              }
+            })
+          )
+        : formik.initialValues.components
+
+    formik.setValues({
+      spfId: recordId,
+      components: updateItemsList
+    })
+  }
+
+  useEffect(() => {
+    ;(async function () {
+      if (recordId) await fetchGridData()
+    })()
+  }, [])
+
+  const columns = [
+    {
+      component: 'numberfield',
+      label: labels.position,
+      name: 'position',
+      props: {
+        decimalScale: 0,
+        allowNegative: false
+      }
+    },
+    {
+      component: 'resourcecombobox',
+      name: 'sectionType',
+      label: labels.sectionType,
+      props: {
+        datasetId: DataSets.SERIAL_PROFILE_SECTION,
+        valueField: 'key',
+        displayField: 'value',
+        mapping: [
+          { from: 'key', to: 'sectionType' },
+          { from: 'value', to: 'sectionTypeName' }
+        ]
+      }
+    },
+    {
+      component: 'textfield',
+      label: labels.str,
+      name: 'str',
+      props: {
+        maxLength: 20
+      }
+    },
+    {
+      component: 'resourcecombobox',
+      name: 'oper',
+      label: labels.oper,
+      props: {
+        datasetId: DataSets.SERIAL_PROFILE_OPERATOR,
+        valueField: 'key',
+        displayField: 'value',
+        mapping: [
+          { from: 'key', to: 'oper' },
+          { from: 'value', to: 'operatorName' }
+        ]
+      },
+      propsReducer({ row, props }) {
+        return {
+          ...props,
+          readOnly: Number(row.sectionType) !== 1
+        }
+      }
+    },
+    {
+      component: 'numberfield',
+      label: labels.size,
+      name: 'size',
+      props: {
+        maxLength: 4,
+        decimalScale: 0
+      }
+    }
+  ]
+
+  return (
+    <FormShell
+      form={formik}
+      resourceId={ResourceIds.SerialNumber}
+      maxAccess={maxAccess}
+      isCleared={false}
+      editMode={editMode}
+      isInfo={false}
+    >
+      <DataGrid
+        onChange={value => formik.setFieldValue('components', value)}
+        value={formik.values.components}
+        error={formik.errors.components}
+        columns={columns}
+        maxAccess={maxAccess}
+      />
+    </FormShell>
+  )
+}
+
+export default SerialProfileSequences
diff --git a/src/pages/iv-serial-profiles/Forms/SerialProfilesForm.js b/src/pages/iv-serial-profiles/Forms/SerialProfilesForm.js
new file mode 100644
index 0000000000..7c05bfd151
--- /dev/null
+++ b/src/pages/iv-serial-profiles/Forms/SerialProfilesForm.js
@@ -0,0 +1,101 @@
+import { Grid } from '@mui/material'
+import { useContext, useEffect } from 'react'
+import * as yup from 'yup'
+import FormShell from 'src/components/Shared/FormShell'
+import toast from 'react-hot-toast'
+import { RequestsContext } from 'src/providers/RequestsContext'
+import { useInvalidate } from 'src/hooks/resource'
+import { ResourceIds } from 'src/resources/ResourceIds'
+import CustomTextField from 'src/components/Inputs/CustomTextField'
+import { useForm } from 'src/hooks/form'
+import { ControlContext } from 'src/providers/ControlContext'
+import { InventoryRepository } from 'src/repositories/InventoryRepository'
+
+export default function SerialProfilesForm({ labels, maxAccess, store, setStore }) {
+  const { recordId } = store
+  const { platformLabels } = useContext(ControlContext)
+
+  const { getRequest, postRequest } = useContext(RequestsContext)
+
+  const invalidate = useInvalidate({
+    endpointId: InventoryRepository.SerialsProfile.page
+  })
+
+  const { formik } = useForm({
+    maxAccess,
+    initialValues: {
+      recordId,
+      name: '',
+      reference: ''
+    },
+    validateOnChange: false,
+    validationSchema: yup.object({
+      reference: yup.string().required(),
+      name: yup.string().required()
+    }),
+    onSubmit: async values => {
+      await postRequest({
+        extension: InventoryRepository.SerialsProfile.set,
+        record: JSON.stringify(values)
+      }).then(res => {
+        toast.success(!values.recordId ? platformLabels.Added : platformLabels.Edited)
+
+        if (!values.recordId) {
+          formik.setFieldValue('recordId', res.recordId)
+          setStore(prevStore => ({
+            ...prevStore,
+            recordId: res.recordId
+          }))
+        }
+        invalidate()
+      })
+    }
+  })
+
+  const editMode = !!formik.values.recordId
+
+  useEffect(() => {
+    ;(async function () {
+      if (recordId) {
+        const res = await getRequest({
+          extension: InventoryRepository.SerialsProfile.get,
+          parameters: `_recordId=${recordId}`
+        })
+        formik.setValues(res.record)
+      }
+    })()
+  }, [])
+
+  return (
+    <FormShell resourceId={ResourceIds.SerialNumber} form={formik} maxAccess={maxAccess} editMode={editMode}>
+      <Grid container spacing={2}>
+        <Grid item xs={12}>
+          <CustomTextField
+            name='reference'
+            label={labels.reference}
+            value={formik.values.reference}
+            required
+            maxAccess={maxAccess}
+            maxLength='10'
+            onChange={formik.handleChange}
+            onClear={() => formik.setFieldValue('reference', '')}
+            error={formik.touched.reference && Boolean(formik.errors.reference)}
+          />
+        </Grid>
+        <Grid item xs={12}>
+          <CustomTextField
+            name='name'
+            label={labels.name}
+            value={formik.values.name}
+            required
+            maxAccess={maxAccess}
+            maxLength='20'
+            onChange={formik.handleChange}
+            onClear={() => formik.setFieldValue('name', '')}
+            error={formik.touched.name && Boolean(formik.errors.name)}
+          />
+        </Grid>
+      </Grid>
+    </FormShell>
+  )
+}
diff --git a/src/pages/iv-serial-profiles/Windows/SerialProfileWindow.js b/src/pages/iv-serial-profiles/Windows/SerialProfileWindow.js
new file mode 100644
index 0000000000..5d595a2b99
--- /dev/null
+++ b/src/pages/iv-serial-profiles/Windows/SerialProfileWindow.js
@@ -0,0 +1,27 @@
+import CustomTabPanel from 'src/components/Shared/CustomTabPanel'
+import { useState } from 'react'
+import { CustomTabs } from 'src/components/Shared/CustomTabs'
+import SerialProfilesForm from '../Forms/SerialProfilesForm'
+import SerialProfileSequences from '../Forms/SerialProfileSequences'
+
+export default function SerialProfileWindow({ labels, maxAccess, recordId }) {
+  const [activeTab, setActiveTab] = useState(0)
+
+  const [store, setStore] = useState({
+    recordId: recordId || null,
+  })
+
+  const tabs = [{ label: labels.SerialNumbersProfiles }, { label: labels.SerialProfileSequences, disabled: !store.recordId }]
+
+  return (
+    <>
+      <CustomTabs tabs={tabs} activeTab={activeTab} setActiveTab={setActiveTab} />
+      <CustomTabPanel index={0} value={activeTab}>
+        <SerialProfilesForm labels={labels} maxAccess={maxAccess} setStore={setStore} store={store} />
+      </CustomTabPanel>
+      <CustomTabPanel index={1} value={activeTab}>
+        <SerialProfileSequences labels={labels} maxAccess={maxAccess} store={store} />
+      </CustomTabPanel>
+    </>
+  )
+}
diff --git a/src/pages/iv-serial-profiles/index.js b/src/pages/iv-serial-profiles/index.js
new file mode 100644
index 0000000000..51014770e8
--- /dev/null
+++ b/src/pages/iv-serial-profiles/index.js
@@ -0,0 +1,114 @@
+import { useContext } from 'react'
+import toast from 'react-hot-toast'
+import Table from 'src/components/Shared/Table'
+import { RequestsContext } from 'src/providers/RequestsContext'
+import { useResourceQuery } from 'src/hooks/resource'
+import { ResourceIds } from 'src/resources/ResourceIds'
+import { VertLayout } from 'src/components/Shared/Layouts/VertLayout'
+import { Fixed } from 'src/components/Shared/Layouts/Fixed'
+import { Grow } from 'src/components/Shared/Layouts/Grow'
+import { useWindow } from 'src/windows'
+import { ControlContext } from 'src/providers/ControlContext'
+import GridToolbar from 'src/components/Shared/GridToolbar'
+import { InventoryRepository } from 'src/repositories/InventoryRepository'
+import SerialProfileWindow from './Windows/SerialProfileWindow'
+
+const SerialProfiles = () => {
+  const { getRequest, postRequest } = useContext(RequestsContext)
+  const { platformLabels } = useContext(ControlContext)
+  const { stack } = useWindow()
+
+  async function fetchGridData(options = {}) {
+    const { _startAt = 0, _pageSize = 50 } = options
+
+    const response = await getRequest({
+      extension: InventoryRepository.SerialsProfile.page,
+      parameters: `_startAt=${_startAt}&_pageSize=${_pageSize}&_filter=`
+    })
+
+    return { ...response, _startAt: _startAt }
+  }
+
+  const {
+    query: { data },
+    refetch,
+    labels,
+    access,
+    paginationParameters,
+    invalidate
+  } = useResourceQuery({
+    queryFn: fetchGridData,
+    endpointId: InventoryRepository.SerialsProfile.page,
+    datasetId: ResourceIds.SerialNumber
+  })
+
+  const columns = [
+    {
+      field: 'reference',
+      headerName: labels.reference,
+      flex: 1
+    },
+    {
+      field: 'name',
+      headerName: labels.name,
+      flex: 1
+    },
+  ]
+
+  function openForm(recordId) {
+    stack({
+      Component: SerialProfileWindow,
+      props: {
+        labels,
+        recordId,
+        maxAccess: access
+      },
+      width: 700,
+      height: 500,
+      title: labels.SerialNumbersProfiles
+    })
+  }
+
+  const edit = obj => {
+    openForm(obj?.recordId)
+  }
+
+  const add = () => {
+    openForm()
+  }
+
+  const del = async obj => {
+    await postRequest({
+      extension: InventoryRepository.SerialsProfile.del,
+      record: JSON.stringify(obj)
+    })
+    toast.success(platformLabels.Deleted)
+    invalidate()
+  }
+
+  return (
+    <VertLayout>
+      <Fixed>
+        <GridToolbar onAdd={add} maxAccess={access} />
+      </Fixed>
+      <Grow>
+        <Table
+          name='table'
+          columns={columns}
+          gridData={data}
+          rowId={['recordId']}
+          onEdit={edit}
+          onDelete={del}
+          isLoading={false}
+          pageSize={50}
+          paginationType='api'
+          paginationParameters={paginationParameters}
+          refetch={refetch}
+          maxAccess={access}
+        />
+      </Grow>
+    </VertLayout>
+  )
+}
+
+export default SerialProfiles
diff --git a/src/repositories/InventoryRepository.js b/src/repositories/InventoryRepository.js
index 4c7757871f..e50b284140 100644
--- a/src/repositories/InventoryRepository.js
+++ b/src/repositories/InventoryRepository.js
@@ -104,11 +104,6 @@ export const InventoryRepository = {
     del: service + 'delDI'
   },
   DimensionId: { set: service + 'set2ID', get: service + 'getID' },
-
-  SerialNumber: {
-    qry: service + 'qrySPF'
-  },
-
   CategoryCurrency: {
     get: service + 'getCAC',
     qry: service + 'qryCAC',
@@ -140,9 +135,6 @@ export const InventoryRepository = {
     set: service + 'setOQ',
     del: service + 'delOQ'
   },
-  SerialProfile: {
-    qry: service + 'qrySPF'
-  },
   ItemProduction: {
     get: service + 'getMFR',
     set: service + 'setMFR'
@@ -326,5 +318,16 @@ export const InventoryRepository = {
   MeasurementSerial: {
     qry: service + 'qryMSM',
     set2: service + 'set2MSM',
+  },
+  SerialsProfile: {
+    qry: service + 'qrySPF',
+    set: service + 'setSPF',
+    get: service + 'getSPF',
+    del: service + 'delSPF',
+    page: service + 'pageSPF',
+    set2: service + 'set2SPF',
+  },
+  SerialsProfileSequences: {
+    qry: service + 'qrySPS',
   }
 }
diff --git a/src/resources/ResourceIds.js b/src/resources/ResourceIds.js
index a3a28351d4..0f312a0d8f 100644
--- a/src/resources/ResourceIds.js
+++ b/src/resources/ResourceIds.js
@@ -460,6 +460,7 @@ export const ResourceIds = {
   BalanceTransferMultiAccounts: 31340,
   SerialsTrackings: 41111,
   JobsInProcess: 42309,
+  SerialNumber: 41109,
   CategoryPriceGroup: 51119,
   PostWorkCenterJob: 42330,
   ProductionShifts: 42113


Reviews:
user: login: Ghinwa-Arab
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T05:39:22Z
user: login: Ghinwa-Arab
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T05:41:20Z
user: login: Ghinwa-Arab
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T05:44:42Z
user: login: Ghinwa-Arab
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T05:46:57Z
user: login: Ghinwa-Arab
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T05:47:45Z
user: login: Ghinwa-Arab
body: 1st review
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T05:48:00Z
user: login: Ghinwa-Arab
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-22T09:34:36Z

Review comments:
diff_hunk: @@ -326,5 +326,15 @@ export const InventoryRepository = {
   MeasurementSerial: {
     qry: service + 'qryMSM',
     set2: service + 'set2MSM',
+  },
+  SerialsProfile: {
path: src/repositories/InventoryRepository.js
user: login: Ghinwa-Arab
body: we have 3 sets for this endpoint (SerialNumber-SerialProfile-SerialsProfile), please merge them into one
created_at: 2025-05-19T05:39:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+import { Grid } from '@mui/material'
+import { useContext, useEffect } from 'react'
+import * as yup from 'yup'
+import FormShell from 'src/components/Shared/FormShell'
+import toast from 'react-hot-toast'
+import { RequestsContext } from 'src/providers/RequestsContext'
+import { useInvalidate } from 'src/hooks/resource'
+import { ResourceIds } from 'src/resources/ResourceIds'
+import CustomTextField from 'src/components/Inputs/CustomTextField'
+import { ManufacturingRepository } from 'src/repositories/ManufacturingRepository'
path: src/pages/iv-serial-profiles/Forms/SerialProfilesForm.js
user: login: Ghinwa-Arab
body: remove ManufacturingRepository import
created_at: 2025-05-19T05:41:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,190 @@
+import { useContext, useEffect } from 'react'
+import { DataGrid } from 'src/components/Shared/DataGrid'
+import FormShell from 'src/components/Shared/FormShell'
+import { RequestsContext } from 'src/providers/RequestsContext'
+import { ResourceIds } from 'src/resources/ResourceIds'
+import toast from 'react-hot-toast'
+import * as yup from 'yup'
+import { useForm } from 'src/hooks/form'
+import { ControlContext } from 'src/providers/ControlContext'
+import { InventoryRepository } from 'src/repositories/InventoryRepository'
+import { DataSets } from 'src/resources/DataSets'
+
+const SerialProfileSequences = ({ store, maxAccess, labels }) => {
+  const { recordId } = store
+  const { getRequest, postRequest } = useContext(RequestsContext)
+  const { platformLabels } = useContext(ControlContext)
+
+  const { formik } = useForm({
+    validateOnChange: true,
+    maxAccess,
+    initialValues: {
+      spfId: recordId,
+      components: [
+        {
+          id: 1,
+          spfId: recordId,
+          position: null,
+          seqNo: 1,
+          str: '',
+          sectionType: null,
+          oper: null,
+          size: null
+        }
+      ]
+    },
+    validationSchema: yup.object({
+      components: yup.array().of(
+        yup.object({
+          position: yup.number().required(),
+          str: yup.string().required(),
+          sectionType: yup.string().required(),
path: src/pages/iv-serial-profiles/Forms/SerialProfileSequences.js
user: login: Ghinwa-Arab
body: should be `.number()`
created_at: 2025-05-19T05:44:42Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,190 @@
+import { useContext, useEffect } from 'react'
+import { DataGrid } from 'src/components/Shared/DataGrid'
+import FormShell from 'src/components/Shared/FormShell'
+import { RequestsContext } from 'src/providers/RequestsContext'
+import { ResourceIds } from 'src/resources/ResourceIds'
+import toast from 'react-hot-toast'
+import * as yup from 'yup'
+import { useForm } from 'src/hooks/form'
+import { ControlContext } from 'src/providers/ControlContext'
+import { InventoryRepository } from 'src/repositories/InventoryRepository'
+import { DataSets } from 'src/resources/DataSets'
+
+const SerialProfileSequences = ({ store, maxAccess, labels }) => {
+  const { recordId } = store
+  const { getRequest, postRequest } = useContext(RequestsContext)
+  const { platformLabels } = useContext(ControlContext)
+
+  const { formik } = useForm({
+    validateOnChange: true,
+    maxAccess,
+    initialValues: {
+      spfId: recordId,
+      components: [
+        {
+          id: 1,
+          spfId: recordId,
+          position: null,
+          seqNo: 1,
+          str: '',
+          sectionType: null,
+          oper: null,
+          size: null
+        }
+      ]
+    },
+    validationSchema: yup.object({
+      components: yup.array().of(
+        yup.object({
+          position: yup.number().required(),
+          str: yup.string().required(),
+          sectionType: yup.string().required(),
+          oper: yup
+            .string()
+            .nullable()
+            .test(function (value) {
+              const { sectionType } = this.parent
+              if (sectionType != 1) return true
+
+              return value
+            }),
+          size: yup.number().min(1).required()
+        })
+      )
+    }),
+    onSubmit: async values => {
+      const modifiedItems = values?.components.map((details, index) => {
+        return {
+          ...details,
+          id: index + 1,
+          seqNo: index + 1,
+          spfId: recordId
+        }
+      })
+
+      await postRequest({
+        extension: InventoryRepository.SerialsProfile.set2,
+        record: JSON.stringify({ spfId: recordId, components: modifiedItems })
+      }).then(() => {
+        fetchGridData()
+        toast.success(platformLabels.Edited)
+      })
+    }
+  })
+
+  const editMode = !!recordId
+
+  async function fetchGridData() {
+    const res = await getRequest({
+      extension: InventoryRepository.SerialsProfileSequences.qry,
+      parameters: `_spfId=${recordId}`
+    })
+
+    const updateItemsList =
+      res?.list?.length !== 0
+        ? await Promise.all(
+            res.list.map(async (item, index) => {
+              return {
+                ...item,
+                id: index + 1,
+                sectionType: Number(item.sectionType)
+              }
+            })
+          )
+        : formik.initialValues.components
+
+    formik.setValues({
+      spfId: recordId,
+      recordId,
path: src/pages/iv-serial-profiles/Forms/SerialProfileSequences.js
user: login: Ghinwa-Arab
body: remove recordId
created_at: 2025-05-19T05:46:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,190 @@
+import { useContext, useEffect } from 'react'
+import { DataGrid } from 'src/components/Shared/DataGrid'
+import FormShell from 'src/components/Shared/FormShell'
+import { RequestsContext } from 'src/providers/RequestsContext'
+import { ResourceIds } from 'src/resources/ResourceIds'
+import toast from 'react-hot-toast'
+import * as yup from 'yup'
+import { useForm } from 'src/hooks/form'
+import { ControlContext } from 'src/providers/ControlContext'
+import { InventoryRepository } from 'src/repositories/InventoryRepository'
+import { DataSets } from 'src/resources/DataSets'
+
+const SerialProfileSequences = ({ store, maxAccess, labels }) => {
+  const { recordId } = store
+  const { getRequest, postRequest } = useContext(RequestsContext)
+  const { platformLabels } = useContext(ControlContext)
+
+  const { formik } = useForm({
+    validateOnChange: true,
+    maxAccess,
+    initialValues: {
+      spfId: recordId,
+      components: [
+        {
+          id: 1,
+          spfId: recordId,
+          position: null,
+          seqNo: 1,
+          str: '',
+          sectionType: null,
+          oper: null,
+          size: null
+        }
+      ]
+    },
+    validationSchema: yup.object({
+      components: yup.array().of(
+        yup.object({
+          position: yup.number().required(),
+          str: yup.string().required(),
+          sectionType: yup.string().required(),
+          oper: yup
+            .string()
+            .nullable()
+            .test(function (value) {
+              const { sectionType } = this.parent
+              if (sectionType != 1) return true
+
+              return value
+            }),
+          size: yup.number().min(1).required()
+        })
+      )
+    }),
+    onSubmit: async values => {
+      const modifiedItems = values?.components.map((details, index) => {
+        return {
+          ...details,
+          id: index + 1,
+          seqNo: index + 1,
+          spfId: recordId
+        }
+      })
+
+      await postRequest({
+        extension: InventoryRepository.SerialsProfile.set2,
+        record: JSON.stringify({ spfId: recordId, components: modifiedItems })
+      }).then(() => {
+        fetchGridData()
+        toast.success(platformLabels.Edited)
+      })
+    }
+  })
+
+  const editMode = !!recordId
+
+  async function fetchGridData() {
+    const res = await getRequest({
+      extension: InventoryRepository.SerialsProfileSequences.qry,
+      parameters: `_spfId=${recordId}`
+    })
+
+    const updateItemsList =
+      res?.list?.length !== 0
+        ? await Promise.all(
+            res.list.map(async (item, index) => {
+              return {
+                ...item,
+                id: index + 1,
+                sectionType: Number(item.sectionType)
+              }
+            })
+          )
+        : formik.initialValues.components
+
+    formik.setValues({
+      spfId: recordId,
+      recordId,
+      components: updateItemsList
+    })
+  }
+
+  useEffect(() => {
+    ;(async function () {
+      if (recordId) await fetchGridData()
+    })()
+  }, [])
+
+  const columns = [
+    {
+      component: 'numberfield',
+      label: labels.position,
+      name: 'position',
+      props: {
+        maxLength: 4,
+        decimalScale: 0
+      }
+    },
+    {
+      component: 'resourcecombobox',
+      name: 'sectionType',
+      label: labels.sectionType,
+      props: {
+        datasetId: DataSets.SERIAL_PROFILE_SECTION,
+        valueField: 'key',
+        displayField: 'value',
+        mapping: [
+          { from: 'key', to: 'sectionType' },
+          { from: 'value', to: 'sectionTypeName' }
+        ]
+      }
+    },
+    {
+      component: 'textfield',
+      label: labels.str,
+      name: 'str',
+      props: {
+        maxLength: 20
+      }
+    },
+    {
+      component: 'resourcecombobox',
+      name: 'oper',
+      label: labels.oper,
+      props: {
+        datasetId: DataSets.SERIAL_PROFILE_OPERATOR,
+        valueField: 'key',
+        displayField: 'value',
+        mapping: [
+          { from: 'key', to: 'oper' },
+          { from: 'value', to: 'operatorName' }
+        ]
+      },
+      propsReducer({ row, props }) {
+        return {
+          ...props,
+          readOnly: Number(row.sectionType) !== 1
+        }
+      }
+    },
+    {
+      component: 'numberfield',
+      label: labels.size,
+      name: 'size',
+      props: {
+        maxLength: 4,
+        decimalScale: 0
+      }
+    }
+  ]
+
+  return (
+    <FormShell
+      form={formik}
+      resourceId={ResourceIds.SerialNumber}
+      maxAccess={maxAccess}
+      isCleared={false}
+      editMode={editMode}
+    >
+      <DataGrid
+        onChange={value => formik.setFieldValue('components', value)}
path: src/pages/iv-serial-profiles/Forms/SerialProfileSequences.js
user: login: Ghinwa-Arab
body: maxAccess missing
created_at: 2025-05-19T05:47:44Z
author_association: COLLABORATOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-16T12:42:05Z
body: [vc]: #F37rT2nADWgucpEJhYG8lH00eqSujfjezLGy8cfZdHE=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJhcmd1cy1mZSIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9nZW9yZ2Uta2FsYXNocy1wcm9qZWN0cy9hcmd1cy1mZS9GcUR2TlRGa2RNUUJzMXhyVTlKdzF3NmkxWDVSIiwicHJldmlld1VybCI6ImFyZ3VzLWZlLWdpdC1hci0zMjA1LWdlb3JnZS1rYWxhc2hzLXByb2plY3RzLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiYXJndXMtZmUtZ2l0LWFyLTMyMDUtZ2VvcmdlLWthbGFzaHMtcHJvamVjdHMudmVyY2VsLmFwcCJ9LCJyb290RGlyZWN0b3J5IjpudWxsfV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **argus-fe** | ✅ Ready ([Inspect](https://vercel.com/george-kalashs-projects/argus-fe/FqDvNTFkdMQBs1xrU9Jw1w6i1X5R)) | [Visit Preview](https://argus-fe-git-ar-3205-george-kalashs-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/argus-fe-git-ar-3205-george-kalashs-projects.vercel.app?via=pr-comment-feedback-link) | May 21, 2025 10:22am |


author_association: NONE
user: login: Ghinwa-Arab
created_at: 2025-05-19T05:20:14Z
body: ![image](https://github.com/user-attachments/assets/737f1dba-fb4a-46e4-b46b-5ddc8da93ae3)

do u need 'clear' and 'save & clear' in first tab?
author_association: COLLABORATOR
user: login: Ghinwa-Arab
created_at: 2025-05-19T05:23:38Z
body: ![image](https://github.com/user-attachments/assets/a631701a-26ba-4c1b-9c3e-a8af5de24e0c)

take into consideration that i can clear all rows& save, so if only one row present with no filled data treat it as optional row
author_association: COLLABORATOR
user: login: Ghinwa-Arab
created_at: 2025-05-19T05:25:26Z
body: ![image](https://github.com/user-attachments/assets/4077feb9-af47-4da7-96a6-0980a953e93b)

maximum value for position field is 20 and it shouldn't accept negative numbers
author_association: COLLABORATOR
user: login: Ghinwa-Arab
created_at: 2025-05-20T09:22:38Z
body: ![image](https://github.com/user-attachments/assets/34e5eb5c-d941-4a87-8b7d-16b61aa56429)

When the section type != 'seq nb', the operation field is readOnly and optional. This behavior works correctly in new mode, but once it's saved (editMode) the operation field incorrectly becomes mandatory.
author_association: COLLABORATOR
user: login: Ghinwa-Arab
created_at: 2025-05-20T12:08:37Z
body: ![image](https://github.com/user-attachments/assets/5d21a9b2-ffd6-4f49-8e53-a6d9096b2089)

no need for the info button in 2nd tab, already present in the first
author_association: COLLABORATOR
user: login: Ghinwa-Arab
created_at: 2025-05-20T12:09:30Z
body: ![image](https://github.com/user-attachments/assets/f8eb14e5-9b03-41e4-af7a-8019366b475b)

i can't delete all rows and save
author_association: COLLABORATOR
user: login: Ghinwa-Arab
created_at: 2025-05-21T07:15:53Z
body: ![image](https://github.com/user-attachments/assets/7a902c9a-6b8a-4bf4-af1d-327ae28cc6b9)

when opening a new mode, after filling in the data and clicking 'save & clear', the data is saved and the form is reset but the second tab becomes enabled
author_association: COLLABORATOR
user: login: Ghinwa-Arab
created_at: 2025-05-21T07:17:06Z
body: ![image](https://github.com/user-attachments/assets/80267ba5-e212-4a9d-99ed-42868fd8355a)

don't give position default value = 0
author_association: COLLABORATOR