4 ```json= [ { "colPos": "0", "desc": "校正儀器名稱", "fieldId": "201101717432791-00", "fieldSeq": "490835839", "height": "500", "isBKM": false, "isBold": true, "isChanged": false, "isLink": false, "isPM50": false, "isTB": false, "rowPos": "0", "totalMergedCells": 8, "type": "label", "width": "500" }, { "colPos": "0", "desc": "Dresser Down Force Load Cell", "fieldId": "202081212523403-00", "fieldSeq": "490835983", "height": "500", "isBKM": false, "isBold": false, "isChanged": false, "isLink": false, "isPM50": false, "isTB": false, "rowPos": "1", "totalMergedCells": 3, "type": "label", "width": "500" }, { "colPos": "3", "desc": "[text](na)", "fieldId": "202081212523403-01", "fieldSeq": "490456892", "height": "500", "inChLoc": "", "isAutoPMStartTime": false, "isAutoToolId": false, "isBarcode": false, "isChanged": false, "isJig": true, "isNone": true, "length": "", "naRfCol": "", "partNum": "", "rowPos": "1", "totalMergedCells": 2, "type": "text", "width": "500" }, { "colPos": "5", "fieldId": "202081212523403-02", "fieldSeq": "490835985", "fileUrl": "https://pmc-partsmiddle-central.gw.f12gpaas.tsmc.com.tw/PartsMiddle/rs/EpmmPmForm/getImageFile?formNo\u003dM-CCM-04-03-029-906\u0026fileName\u003d20230709155558.png", "filename": "Dresser Jig 20230607.png", "height": "500", "isChanged": false, "rowPos": "1", "totalMergedCells": 3, "type": "image", "width": "1150" }, { "colPos": "0", "desc": "1. Check Downforce 是否符合規格", "fieldId": "2011017171720606-00", "fieldSeq": "490835835", "height": "500", "isBKM": false, "isBold": true, "isChanged": false, "isLink": false, "isPM50": false, "isTB": false, "rowPos": "2", "totalMergedCells": 8, "type": "label", "width": "500" }, { "colPos": "0", "desc": "Pol-A (Spec:18.0~22.0N)", "fieldId": "201101914532881-00", "fieldSeq": "490835843", "height": "500", "isBKM": false, "isBold": true, "isChanged": false, "isLink": false, "isPM50": false, "isTB": false, "rowPos": "3", "totalMergedCells": 1, "type": "label", "width": "500" },{ "ECSVID": "", "ECSVName": "", "autoFillName": "", "autoFillType": "", "colPos": "1", "decimal": "", "description": "dresser down force u1_Pressure_1", "fieldId": "201101914532881-01", "fieldSeq": "490835844_1", "fillInType": "", "height": "500", "isAutoCopyByName": false, "isAutoFillECSV": false, "isChanged": false, "isECSVID": true, "isECSVName": false, "isNone": true, "maxValue": "22", "minValue": "18", "muliply": "", "rowPos": "3", "source": "", "timing": "", "totalMergedCells": 1, "type": "numeric", "width": "500" }, { "colPos": "2", "desc": "Pol-B (Spec:13.0~17.0N)", "fieldId": "201101914532881-02", "fieldSeq": "490835845", "height": "500", "isBKM": false, "isBold": false, "isChanged": false, "isLink": false, "isPM50": false, "isTB": false, "rowPos": "3", "totalMergedCells": 1, "type": "label", "width": "500" },{ "ECSVID": "", "ECSVName": "", "autoFillName": "", "autoFillType": "", "colPos": "3", "decimal": "", "description": "dresser down force u1_Pressure_2", "fieldId": "201101914532881-01", "fieldSeq": "490835844_2", "fillInType": "", "height": "500", "isAutoCopyByName": false, "isAutoFillECSV": false, "isChanged": false, "isECSVID": true, "isECSVName": false, "isNone": true, "maxValue": "17", "minValue": "13", "muliply": "", "rowPos": "3", "source": "", "timing": "", "totalMergedCells": 1, "type": "numeric", "width": "500" }, { "colPos": "4", "desc": "Pol-C (Spec:18.0~22.0N)", "fieldId": "201101914532881-04", "fieldSeq": "490835847", "height": "500", "isBKM": false, "isBold": false, "isChanged": false, "isLink": false, "isPM50": false, "isTB": false, "rowPos": "3", "totalMergedCells": 1, "type": "label", "width": "500" },{ "ECSVID": "", "ECSVName": "", "autoFillName": "", "autoFillType": "", "colPos": "5", "decimal": "", "description": "dresser down force u2_Pressure_1", "fieldId": "201101914532881-05", "fieldSeq": "490835848", "fillInType": "", "height": "500", "isAutoCopyByName": false, "isAutoFillECSV": false, "isChanged": false, "isECSVID": true, "isECSVName": false, "isNone": true, "maxValue": "22", "minValue": "18", "muliply": "", "rowPos": "3", "source": "", "timing": "", "totalMergedCells": 1, "type": "numeric", "width": "500" }, { "colPos": "6", "desc": "Pol-D (Spec:13.0~17.0N)", "fieldId": "201101914532881-06", "fieldSeq": "490835849", "height": "500", "isBKM": false, "isBold": false, "isChanged": false, "isLink": false, "isPM50": false, "isTB": false, "rowPos": "3", "totalMergedCells": 1, "type": "label", "width": "500" },{ "ECSVID": "", "ECSVName": "", "autoFillName": "", "autoFillType": "", "colPos": "7", "decimal": "", "description": "dresser down force u2_Pressure_2", "fieldId": "201101914532881-07", "fieldSeq": "490835850", "fillInType": "", "height": "500", "isAutoCopyByName": false, "isAutoFillECSV": false, "isChanged": false, "isECSVID": true, "isECSVName": false, "isNone": true, "maxValue": "17", "minValue": "13", "muliply": "", "rowPos": "3", "source": "", "timing": "", "totalMergedCells": 1, "type": "numeric", "width": "500" }, { "colPos": "0", "desc": "7.3 檢查清潔 Polish-ABCD FAC drain 管 \n※於2,4,6,8,10,12月份執行", "fieldId": "2020812122047399-00", "fieldSeq": "490835968", "height": "500", "isBKM": false, "isBold": false, "isChanged": false, "isLink": false, "isPM50": false, "isTB": false, "rowPos": "4", "totalMergedCells": 4, "type": "label", "width": "500" },{ "colPos": "4", "fieldId": "2020812122047399-01", "fieldSeq": "490835969", "height": "500", "isChanged": false, "layout": "horizontal", "radioButtonGroup":[ { "desc": "執行", "sequence": "0"}, { "desc": "不需執行", "sequence": "1" } ], "rowPos": "4", "selectedRadioBtn": "", "template": "none", "totalMergedCells": 4, "type": "radioButton", "width": "500" } ,{ "colPos": "1", "fieldId": "2020812122047399-01", "fieldSeq": "8888888888", "height": "500", "isChanged": false, "layout": "horizontal", "radioButtonGroup":[ { "desc": "V", "sequence": "0"}, { "desc": "X", "sequence": "1" }, { "desc": "NA", "sequence": "2" } ], "rowPos": "5", "selectedRadioBtn": "", "template": "none", "totalMergedCells": 4, "type": "radioButton", "width": "500" } ,{ "colPos": "1", "fieldId": "2020812122047399-01", "fieldSeq": "99999999999", "height": "500", "isChanged": false, "layout": "horizontal", "radioButtonGroup":[ { "desc": "V", "sequence": "0"}, { "desc": "X", "sequence": "1" }, { "desc": "NA", "sequence": "2" }, { "desc": "NAAAA", "sequence": "3" } ], "rowPos": "6", "selectedRadioBtn": "", "template": "none", "totalMergedCells": 4, "type": "radioButton", "width": "500" } ] ``` ```typescript= import { useRef, useState, useEffect} from 'react'; import { useHistory } from 'react-router-dom'; import { Workbook, WorkbookInstance } from "@fortune-sheet/react"; import "@fortune-sheet/react/dist/index.css"; import _formJsonData from "./data/FormJsonData.json"; import { Util } from './model/Util'; import { FieldSrc } from './model/FieldSrc' import { LabelSrc } from './model/LabelSrc'; import { TextSrc } from './model/TextSrc'; import { NumericSrc } from './model/Numeric'; import { RadioButtonSrc } from './model/RadioButton'; import { ImageSrc } from './model/ImageSrc'; import "./style.css"; import { Alert, Button, Dialog } from '@mui/material'; import { LocaleTW, LocaleEn } from './locale/formEditorLocale'; type ArrayElement = FieldSrc | LabelSrc | TextSrc | NumericSrc | RadioButtonSrc | ImageSrc; const useConsoleLog = false; const isShiftRightWhenLoading = true; // ----------------------- export default function FormLinker() { const ref = useRef<WorkbookInstance>(null); const history = useHistory(); const [language, setLanguage] = useState("TW"); const [locale, setLocale] = useState(language === "TW" ? new LocaleTW : new LocaleEn); const [lastRow, setLastRow] = useState(-1); const [lastCol, setLastCol] = useState(-1); const [showRightSideBlock, setShowRightSideBlock] = useState(false); const [alertBack, setAlertBack] = useState(false); const [alertRefresh, setAlertRefresh] = useState(false); // 一開始存取localStorage 存的” linkageData”,當作呈現在左側表格的資料 // 此資料為在FormEditor(/edit-form)中,按下[SAVE]後所存的資料 const [importData, setImportData] = useState(localStorage.getItem("linkageData")); const [exportJson, setExportJson] = useState(updateLastExportDataPosition()); const [isLoading, setIsLoading] = useState(true); // ------------------------------ [Custom Toolbar Items]---------------------------- const customToolbarItems_Linkage = [ { // 返回上一頁 key: "BACK", tooltip: "Back", icon: Util.ICON_LEFT, onClick: async () => { setAlertBack(true); } }, { // 重新整理 (回到上次儲存的Data) key: "REFRESH", tooltip: "Refresh", icon: Util.ICON_REFRESH, onClick: async () => { setAlertRefresh(true); } }, { // 編輯Auto NA 設定 key: "EDIT", tooltip: "Edit", icon: Util.ICON_EDIT, onClick: async () => { let rowRange = ref.current?.getSelection()?.at(0)?.row; let colRange = ref.current?.getSelection()?.at(0)?.column; if (rowRange && colRange) { // 限定只有radioButton才可以編輯 let type = checkType(Number(rowRange[0]), Number(colRange[0])); if (type === Util.C_CELL_TYPE_RADIOBUTTON) { handleAutoNA(Number(rowRange[0]), Number(colRange[0])); } } } }, { // 存成json格式 key: "SAVE", tooltip: "Save", icon: Util.ICON_SAVE, onClick: async () => { handleSaveData(); } }, ]; // ------------------------------------------------------------------ function updateLastExportDataPosition() { /**修正目前所有欄位的位置 * 比對importData的fieldSeq, 去更新lastExportData的[row, col] * 包括本cell與linkedCell */ let lastExportData = localStorage.getItem("linkedCellSettings"); if ( lastExportData && importData) { let linkedCellData = JSON.parse(lastExportData); let allCellsData = JSON.parse(structuredClone(importData)); for( let index = 0; index < linkedCellData.length; index++ ) { let curCell = linkedCellData[index]; // Find the matching object in allCellsData let cell = allCellsData.filter((cell: { fieldSeq: string }) => cell.fieldSeq === curCell.fieldSeq); if ( cell && cell[0]) { // 本cell linkedCellData[index].row = Number(cell[0].rowPos); linkedCellData[index].col = Number(cell[0].colPos); if (linkedCellData[index]['linkedCell'] && linkedCellData[index]['linkedCell'][0]){ for( let seq = 0; seq < linkedCellData[index]['linkedCell'].length; seq++ ) { let obj = linkedCellData[index]['linkedCell'][seq]; // Find the matching object in allCellsData let matchCell = allCellsData.filter((cell: { fieldSeq: string }) => cell.fieldSeq === obj.seq); if ( obj && matchCell && matchCell[0] ) { obj.r = Number(matchCell[0].rowPos); obj.c = Number(matchCell[0].colPos); } else { // 之前linked的Cell被刪除或改成其他type linkedCellData[index]['linkedCell'].splice(seq, 1); // 刪除之前linked的Cell } } // for } } } return linkedCellData; } return []; } // --------------------[LOAD]--------------------------------------------- const LoadData = async () => { if (importData) { let jsonData = JSON.parse(importData); loadDataFromJson(jsonData); } else { loadDataFromJson(_formJsonData); } } useEffect(() => { LoadData().then(() => setIsLoading(false)) .then(() => setLinkedCellSettings(structuredClone(exportJson))) .then(() => initFontBackgroundColor()); }, []) // ------------------------------------------------------------------------ // -------------------------[Font/Background Color]------------------------- function setCellColor(row: number, col: number, color: string) { ref.current?.setCellFormat(row, col, "bg", color); } function setCellFontColor(row: number, col: number, color: string) { ref.current?.setCellFormat(row, col, "fc", color); } function initFontBackgroundColor() { /** Highlight radioButton cell * radioButton: 背景淺藍,字黑色 * 其餘: 灰色 * */ let allLinkedCells = exportJson? exportJson: linkedCellSettings; const sheet = ref.current?.getAllSheets(); if (sheet) { // find last non-null row & col const pos = findLastNonNullRowAndColumn(sheet[0].data); // pos[0]: row, pos[1]: col setLastRow(pos[0]); setLastCol(pos[1]); let borderRow = pos[0] + 1; let borderCol = pos[1] + 1; if (typeof borderRow === "undefined" || typeof borderCol === "undefined") return; // traverse all cell, set font/background color for (let row = 0; row < borderRow; row++) { for (let col = 0; col < borderCol; col++) { setCellColor(row, col, "white"); let type = checkType(row, col); switch (type) { case Util.C_CELL_TYPE_RADIOBUTTON: let cell = allLinkedCells.filter((cell: { row: number; col: number; }) => cell.row === row && cell.col === col); // 已有設定連動的cell 設為藍色 setCellColor(row, col, Util.BACKGROUND_COLOR_LIGHT_BLUE); // 尚未設定連動的為淺藍 if (cell && cell.length > 0) { for( let index = 0; index < cell.length; index++ ) { if ( cell[index].linkedCell.length > 0 ) { setCellColor(row, col, Util.BACKGROUND_COLOR_BLUE); // 設定連動的為BLUE break; } } } // if // else setCellColor(row, col, Util.BACKGROUND_COLOR_LIGHT_BLUE); // 尚未設定連動的為淺藍 break; default: setCellFontColor(row, col, "#DCDCDC"); } // switch } } } // if } function setOperableCellColor(op: string, linkedCellRow: number, linkedCellCol: number) { /** * [op] * "on": Highlight可進行連動的欄位(text, numeric, radioBtn) : 背景淺藍, 字黑色 * 目前進行設定的radioBtn Cell: 黃色 * "off": 還原cell顯示: * RadioBtn: * 已有設定連動: 藍色 * 尚未設定連動: 淺藍 * Others: * 背景白色,字灰色 * */ let color = op === "on" ? Util.BACKGROUND_COLOR_LIGHT_BLUE : "white"; let fontColor = op === "on" ? "black" : Util.BACKGROUND_COLOR_GREY; for (let row = 0; row <= lastRow; row++) { for (let col = 0; col <= lastCol; col++) { let type = checkType(row, col); switch (type) { case Util.C_CELL_TYPE_TEXTINPUT: setCellColor(row, col, color); setCellFontColor(row, col, fontColor); break; case Util.C_CELL_TYPE_NUMINPUT: setCellColor(row, col, color); setCellFontColor(row, col, fontColor); break; case Util.C_CELL_TYPE_RADIOBUTTON: if (op === "on") setCellColor(row, col, color); else { const cell = linkedCellSettings.filter((cell: { row: number; col: number; }) => cell.row === row && cell.col === col); if (cell && cell.length > 0) { // 已有設定連動的cell: 藍色, setCellColor(row, col, Util.BACKGROUND_COLOR_BLUE); } else { // 尚未設定連動: 淺藍 setCellColor(row, col, Util.BACKGROUND_COLOR_LIGHT_BLUE); } } // else break; default: // setCellColor(row, col, "#DCDCDC"); } // switch } } if (linkedCellRow !== -1 && linkedCellCol !== -1) { // 目前進行設定的radioBtn Cell: 黃色 setCellColor(linkedCellRow, linkedCellCol, Util.BACKGROUND_COLOR_YELLOW); } // if } // ------------------------------------------------------------------------ // 目前進行設定的radioBtn Cell const [linkedCell, setLinkedCell] = useState(new RadioButtonSrc()); const [curRow, setCurRow] = useState(-1); const [curCol, setCurCol] = useState(-1); // 儲存每個選項對應的欄位設定 let lastLinkedCellData = localStorage.getItem("linkedCellSettings"); const [linkedCellSettings, setLinkedCellSettings] = useState(lastLinkedCellData ? JSON.parse(lastLinkedCellData) : [{ fieldSeq: "", row: -1, col: -1, index: -1, linkedCell: [{ r: -1, c: -1, seq: "" }] }]); function loading() { return ( <div> <div className="loading-container"> <div className="loading-text"> <span>L</span> <span>O</span> <span>A</span> <span>D</span> <span>I</span> <span>N</span> <span>G</span> </div> </div> </div> ); } return ( <div className='container' > {/* 左分欄 */} <div className="leftside block" style={{ width: '70%' }}> {isLoading ? <div>{loading()}</div> : <></>} <Workbook ref={ref} data={[{ name: 'Linkage', config: { rowReadOnly: {}, colReadOnly: { 0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1 } } }]} showSheetTabs={false} toolbarItems={[]} customToolbarItems={customToolbarItems_Linkage} showFormulaBar={false} /> </div> { /** 右分欄 */} <div className="rightside block" style={{ left: '70%' }}> <div className='rightside-title'> <h2>[設定欄位連動]</h2></div> {showRightSideBlock ? <div>{linkHints()}</div> : <div>{startHints()}</div>} {showRightSideBlock && linkedCell && typeof (linkedCell.radioButtonGroup) !== "undefined" && linkedCell.radioButtonGroup.length !== 0 ? ( Array.from({ length: linkedCell.radioButtonGroup.length }).map((_, index) => { const boxId = `${linkedCell.rowPos}-${linkedCell.colPos}-${index}`; return ( <div key={index} className="box" id={boxId}> <div className="box-header"> {linkedCell.radioButtonGroup ? linkedCell.radioButtonGroup[index].desc : null} </div> <div className="box-content"> {linkedCellSettings .filter((cellSetting: { row: number; col: number; index: number; }) => cellSetting.row === Number(linkedCell.rowPos) && cellSetting.col === Number(linkedCell.colPos) && cellSetting.index === index) .map((cellSetting: { linkedCell: any[]; }) => cellSetting.linkedCell.map((linkedCellItem) => ( <div key={`${linkedCellItem.r}-${linkedCellItem.c}`} className="linked-cell"> {getLabel(linkedCellItem.r, linkedCellItem.c)} </div> ))) } </div> <div className="box-buttons"> <Button variant="outlined" onClick={() => handleAddLinkedCell(index)}>Add</Button> <Button variant="outlined" onClick={() => handleDeleteLinkedCell(index)}>Delete</Button> </div> </div> ); }) ) : null } </div> { /** 分割線 */} <div className="divider" style={{ left: '70%' }} /> { alertBack ? <> <Dialog open={alertBack}> <Alert variant="outlined" severity="warning"> {locale.Back_Alert} <Button variant="outlined" onClick={handleCancelBack}>{locale.ConfigBaseRender_btnCancel}</Button> <Button variant="outlined" onClick={handleBack}>{locale.YES}</Button> </Alert> </Dialog> </> : <></> } { alertRefresh ? <> <Dialog open={alertRefresh}> <Alert variant="outlined" severity="warning"> {locale.Refresh_Alert} <Button variant="outlined" onClick={handleCancelRefresh}>{locale.ConfigBaseRender_btnCancel}</Button> <Button variant="outlined" onClick={handleRefresh}>{locale.YES}</Button> </Alert> </Dialog> </> : <></> } </div> ); // ---------------------------------------- function showEditBlock() { setShowRightSideBlock(true); } function hideEditBlock() { setShowRightSideBlock(false); } function handleAutoNA(row: number, col: number) { if (useConsoleLog) console.log("Start handleAutoNA [+]"); setCurRow(row); setCurCol(col); showEditBlock(); setOperableCellColor("on", Number(row), Number(col)); // 設定目前進行設定的radioBtn Cell -> linkedCell let cell = getCellData(row, col); let curCell = cell.at(0); if (cell && curCell && curCell.radioButtonGroup) { setLinkedCell(structuredClone(curCell)); } // if if (useConsoleLog) console.log("End handleAutoNA [-]"); } function isLinkedCellExist(row: number, col: number) { if ( row === -1 && col === -1 ) return false; return true; } function getLabel(row: number, col: number) { if ( isLinkedCellExist(row, col) ) return '[' + getLabelName( row, col)+ ']'; else return ''; } function getLabelName(row: number, col: number) { /** 取得欄名列號 e.g. [3, 7] -> H4 */ let label = ""; const A_CODE = 65; // ASCII code for 'A' const colLetter = A_CODE + col; label = String.fromCharCode(colLetter) + (row + 1).toString(); return label; } function handleAddLinkedCell(index: number) { /**加入欲連動的欄位([row, col]) * index為目前radio button的第幾個選項,(從0開始) */ // 取得所選取的欄位的[row, col] let rowRange = ref.current?.getSelection()?.at(0)?.row; let colRange = ref.current?.getSelection()?.at(0)?.column; if (rowRange && colRange) { let row = rowRange[0]; let col = colRange[0]; // 只能連動text, numeric, radioBtn let type = checkType(row, col); if (type != Util.C_CELL_TYPE_NUMINPUT && type != Util.C_CELL_TYPE_RADIOBUTTON && type != Util.C_CELL_TYPE_TEXTINPUT) { return; } // if // 自己這一格不用連動 if (row === curRow && col === curCol) return; let curCellSetting = linkedCellSettings.find((cell: { row: number; col: number; index: number; }) => cell.row === curRow && cell.col === curCol && cell.index === index); if (curCellSetting) { // update existing setting setLinkedCellSettings((prevState: any[]) => prevState.map(setting => { if (setting === curCellSetting) { // 避免重複加入相同[row, col] if (!setting.linkedCell.some((linked: { r: number; c: number; }) => linked.r === row && linked.c === col)) { let cell = getCellData(row, col); let curCell = cell.at(0); if (cell && curCell) { return { ...setting, linkedCell: [ ...setting.linkedCell, { r: row, c: col, seq: curCell.fieldSeq } ] }; } // if } } return setting; })); } else { // create new setting let cell = getCellData(curRow, curCol); let curCell = cell.at(0) let addCell = getCellData(row, col).at(0); if (cell && curCell && addCell) { setLinkedCellSettings((prevState: any) => [ ...prevState, { fieldSeq: curCell?.fieldSeq || "", row: curRow, col: curCol, index: index, linkedCell: [{ r: row, c: col, seq: addCell?.fieldSeq || "" }] } ]); } // if } // else } // if } function handleDeleteLinkedCell(index: number) { /**取消該欄位([row, col])連動 * index為目前radio button的第幾個選項,(從0開始) */ let rowRange = ref.current?.getSelection()?.at(0)?.row; let colRange = ref.current?.getSelection()?.at(0)?.column; if (rowRange && colRange) { let row = rowRange[0]; let col = colRange[0]; setLinkedCellSettings((prevState: any[]) => { // 找到目前進行設定的radioBtn Cell (with相同的row, col, index) let settingIndex = prevState.findIndex(cell => cell.row === curRow && cell.col === curCol && cell.index === index); if (settingIndex !== -1) { // 將欲刪除[row, col]以外的所有cell存入newLinkedCell中 let newLinkedCell = prevState[settingIndex].linkedCell.filter((cell: { r: number; c: number; }) => !(cell.r === row && cell.c === col)); if (newLinkedCell.length === 0) { // 若newLinkedCell是空的, 則刪除所有設定 return [...prevState.slice(0, settingIndex), ...prevState.slice(settingIndex + 1)]; } else { // 將舊的linkedCell更新為newLinkedCell return [ ...prevState.slice(0, settingIndex), { ...prevState[settingIndex], linkedCell: newLinkedCell }, ...prevState.slice(settingIndex + 1) ]; } } return prevState; }); } } function handleSaveData() { hideEditBlock(); setOperableCellColor("off", -1, -1); setExportJson(structuredClone(linkedCellSettings)); localStorage.setItem("linkedCellSettings", JSON.stringify(structuredClone(linkedCellSettings))); console.log(linkedCellSettings); } function startHints() { return ( <div className="icon-text"> <div> 請於左側選擇要設定連動的欄位 </div> <div> 再按左上角的 {Util.ICON_EDIT_MINI} 圖示,進行設定。 </div> <br /><br /> <h2>[儲存連動設定]</h2> <div>按下左上角 {Util.ICON_SAVE_MINI} 圖示,即儲存欄位連動設定 </div> <br /><br /> <h2>[重新整理]</h2> <div>按下左上角 {Util.ICON_REFRESH_MINI} 圖示,即回到上次儲存的連動資料 </div> <div style={{ color: "red" }}>注意: 未儲存的資料將會遺失</div> </div> ); } function linkHints() { return ( <div className="icon-text"> <div> 請先於左側選擇要連動的欄位 </div> <div> 按下[ADD],加入該選項連動, </div> <div> 按下[Delete],取消該選項連動 </div> </div> ); } function handleBack() { setAlertBack(false); history.push("/"); } function handleCancelBack() { setAlertBack(false); } function handleRefresh() { if (useConsoleLog) console.log('Start handleRefresh [+]'); hideEditBlock(); setAlertRefresh(false); // clear all cells, unmerge cells for (let row = 0; row <= lastRow; row++) { for (let col = 0; col <= lastCol; col++) { ref.current?.clearCell(row, col); ref.current?.setCellFormat(Number(row), Number(col), "fc", "black"); ref.current?.setCellFormat(Number(row), Number(col), "bg", 'white'); } } let rowRange = [0, lastRow]; let colRange = [0, lastCol]; ref.current?.cancelMerge([{ row: rowRange, column: colRange }]); resetLinkedCellSettings(); // reLoad data, set font/background Color LoadData().then(() => initFontBackgroundColor()); if (useConsoleLog) console.log('End handleRefresh [-]'); } function handleCancelRefresh() { setAlertRefresh(false); } function resetLinkedCellSettings() { /**將linkedCellSettings清空,並更新為上次儲存的連動資料 ( exportJson ) */ if (linkedCellSettings != null) { while (linkedCellSettings.length > 0) { linkedCellSettings.pop(); } } // if if (exportJson) { for (let index = 0; index < exportJson.length; index++) { linkedCellSettings.push(exportJson[index]); } } } function checkType(row: number, col: number) { let cell: any; if (importData) { cell = JSON.parse(importData).filter((data: { rowPos: string; colPos: string; }) => data.rowPos === row.toString() && data.colPos === col.toString()); } // if else { cell = _formJsonData.filter(data => data.rowPos === row.toString() && data.colPos === col.toString()); } // else return cell.at(0)?.type; } function getCellData(row: number, col: number) { let cell: any; if (importData) { cell = JSON.parse(importData).filter((data: { rowPos: string; colPos: string; }) => data.rowPos === row.toString() && data.colPos === col.toString()); } // if else { cell = _formJsonData.filter(data => data.rowPos === row.toString() && data.colPos === col.toString()); } // else return cell; } // ---------------------------------------- // Load data from json file async function loadDataFromJson(jsonData: any) { // for shiftRight let lastCol = -1; let lastRow = -1; for (let i = 0; i < jsonData.length; i++) { let data = jsonData[i]; let curCol = Number(data.colPos); let dataInput; switch (data.type) { case Util.C_CELL_TYPE_LABEL: dataInput = (data as LabelSrc); break; case Util.C_CELL_TYPE_TEXTINPUT: dataInput = (data as TextSrc); break; case Util.C_CELL_TYPE_NUMINPUT: dataInput = (data as NumericSrc); break; case Util.C_CELL_TYPE_RADIOBUTTON: dataInput = (data as RadioButtonSrc); break; case Util.C_CELL_TYPE_IMAGE: dataInput = (data as ImageSrc); break; default: ; } if (data.rowPos && data.colPos) { if (isShiftRightWhenLoading) { if (lastRow !== Number(data.rowPos)) { // diff row lastCol = -1; } // if else if (lastCol != -1 && curCol <= lastCol) { data.colPos = (lastCol + 1).toString(); } } if ( typeof dataInput !== "undefined" ) showDataInCell(data.type, dataInput, Number(data.rowPos), Number(data.colPos)); let rowRange = [Number(data.rowPos), Number(data.rowPos)]; let colRange = [Number(data.colPos), Number(data.colPos) + Number(data.totalMergedCells) - 1]; // console.log(rowRange, colRange); showMergeCell(rowRange, colRange); } // if lastCol = Number(data.colPos) + Number(data.totalMergedCells) - 1; lastRow = Number(data.rowPos); } // end for } function showMergeCell(rowRange: number[], colRange: number[]) { ref.current?.mergeCells([{ row: [rowRange[0], rowRange[0]], column: colRange }], "merge-all"); } function showDataInCell(type: string, data: ArrayElement, row: Number, col: Number) { if (useConsoleLog) console.log('Start showDataInCell [+]'); // console.log(data) switch (type) { case Util.C_CELL_TYPE_LABEL: let data_label = (data as LabelSrc); ref.current?.setCellValue(Number(row), Number(col), data_label?.desc); for (let seq = 0; seq < data_label.totalMergedCells; seq++) { if (data_label?.isBold) ref.current?.setCellFormat(Number(row), Number(col) + seq, "bl", 1); else ref.current?.setCellFormat(Number(row), Number(col) + seq, "bl", 0); if (data_label?.isTB || data_label?.isBKM) ref.current?.setCellFormat(Number(row), Number(col) + seq, "bg", 'yellow'); else ref.current?.setCellFormat(Number(row), Number(col) + seq, "bg", 'white'); if (data_label?.isPM50) ref.current?.setCellFormat(Number(row), Number(col) + seq, "fc", "blue"); else ref.current?.setCellFormat(Number(row), Number(col) + seq, "fc", "black"); } break; case Util.C_CELL_TYPE_TEXTINPUT: let data_text = (data as TextSrc); let outputDescText = generateOutputDesc(data_text, Util.C_CELL_TYPE_TEXTINPUT); ref.current?.setCellValue(Number(row), Number(col), outputDescText); break; case Util.C_CELL_TYPE_NUMINPUT: let data_numeric = (data as NumericSrc); let outputDescNumeric = generateOutputDesc(data_numeric, Util.C_CELL_TYPE_NUMINPUT); ref.current?.setCellValue(Number(row), Number(col), outputDescNumeric ? outputDescNumeric : ''); break; case Util.C_CELL_TYPE_RADIOBUTTON: let data_radioBtn = (data as RadioButtonSrc); let outputDescRadioBtn = generateOutputDesc(data_radioBtn, Util.C_CELL_TYPE_RADIOBUTTON); ref.current?.setCellValue(Number(row), Number(col), outputDescRadioBtn ? outputDescRadioBtn : ''); break; case Util.C_CELL_TYPE_IMAGE: let data_image = (data as ImageSrc); let outputDescImage = generateOutputDesc(data_image, type); ref.current?.setCellValue(Number(row), Number(col), outputDescImage ? outputDescImage : ''); break; } if (useConsoleLog) console.log('End showDataInCell [-]'); } function generateOutputDesc(fieldData: FieldSrc, type: string) { if (useConsoleLog) console.log('Start generateOutputDesc [+]'); let outputDesc = ''; switch (type) { case Util.C_CELL_TYPE_TEXTINPUT: let fieldData_text = (fieldData as TextSrc); outputDesc = "[text]("; if (fieldData_text.isNone) outputDesc += 'na'; else if (fieldData_text.isBarcode) outputDesc += 'autoFillin'; else if (fieldData_text.isAutoToolId) outputDesc += 'toolId'; else if (fieldData_text.isAutoPMStartTime) outputDesc += 'actlStartTime'; else if (fieldData_text.isJig) outputDesc += 'jig'; outputDesc += ')'; break; case Util.C_CELL_TYPE_NUMINPUT: let fieldData_numeric = (fieldData as NumericSrc); outputDesc = "[NUMERIC][" + (fieldData_numeric?.minValue || 'NAN') + ',' + (fieldData_numeric?.maxValue || 'NAN') + ']'; if (fieldData_numeric?.isAutoFillECSV) { outputDesc += '(autoFillIn:'; if (fieldData_numeric?.source === 'RPA') { outputDesc += 'RPA'; } else { // console.log('fieldData_numeric?.source:', fieldData_numeric?.source); if (fieldData_numeric?.source === 'PMS-SV') outputDesc += 'PMS-SV'; else if (fieldData_numeric?.source === 'TOOL-SV') outputDesc += 'Tool-SV'; else if (fieldData_numeric?.source === 'TOOL-EC') outputDesc += 'Tool-EC'; if (fieldData_numeric?.isECSVID) outputDesc += 'ID'; else outputDesc += 'NAME'; } outputDesc += ')'; } break; case Util.C_CELL_TYPE_RADIOBUTTON: // ☐ let fieldData_radioBtn = (fieldData as RadioButtonSrc); if (fieldData_radioBtn.radioButtonGroup) { for (let index = 0; index < fieldData_radioBtn.radioButtonGroup.length; index++) { outputDesc += ('☐ ' + fieldData_radioBtn.radioButtonGroup[index].desc + '\n'); } // for } break; case Util.C_CELL_TYPE_IMAGE: let fieldData_image = (fieldData as ImageSrc); if (fieldData_image.filename) outputDesc = fieldData_image.filename; break; } if (useConsoleLog) console.log('End generateOutputDesc [-]'); return outputDesc; } function findLastNonNullRowAndColumn(data: any[] | undefined): [number, number] { if (data) { let lastRow = data.length - 1; let lastColumn = data[0].length - 1; // Find last non-null row for (let i = lastRow; i >= 0; i--) { if (data[i].some((value: null) => value !== null)) { lastRow = i; break; } } // Find last non-null column for (let j = lastColumn; j >= 0; j--) { if (data.some((row) => row[j] !== null)) { lastColumn = j; break; } } return [lastRow, lastColumn]; } else return [-1, -1]; } } ``` style.css ```css= .radio-group { display: flex; flex-direction: column; margin-bottom: 10px; } .radio-group input[type="radio"] { margin-right: 5px; } .vertical-btn { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100px; width: 100px; } .radio-group>* { margin-right: 10px; /* add some space between buttons */ } .section { display: -webkit-flex; display: flex; } .image-display { height: max-content; width: 20rem; background-color: #f1f1f1; padding: 10px; } .component { display: flex; justify-content: center; align-items: center; flex: 1; height: 100%; } /* ---------------------------- */ .container { position: relative; height: 100%; } .divider { position: absolute; top: 0; bottom: 0; width: 1px; height: 100%; background-color: #000; /* cursor: col-resize; */ z-index: 1; } .block { position: absolute; top: 0; bottom: 0; overflow: auto; } .leftside { left: 0; background-color: #ddd; } .rightside { right: 0; background-color: #eee; } /* BOX */ .box-content { background-color: #ffffff; border: 1px solid #dcdcdc; padding: 10px; margin: 10px; box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2); } .box-header { margin: 10px; } .box-buttons{ margin: 10px; } .linked-cell{ display: inline-block; text-indent:5px; } .rightside-title { text-align: center; align-items: center; } .icon-text { text-align: center; } /* Loading Page */ @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@500&display=swap'); * { box-sizing: border-box; } loading_body { width: fit-content; height: 100vh; display: flex; justify-content: center; align-items: center; background-image: radial-gradient( circle farthest-corner at 10% 20%, rgba(0,152,155,1) 0.1%, rgba(0,94,120,1) 94.2% ); background-size: 100%; font-family: 'Montserrat', sans-serif; overflow: hidden; } .loading-container { width: 100%; max-width: 520px; text-align: center; color: #fff; position: relative; margin: 0 32px; } .loading-container:before { content: ''; position: absolute; width: 100%; height: 3px; background-color: #fff; bottom: 0; left: 0; border-radius: 10px; animation: movingLine 2.4s infinite ease-in-out; } @keyframes movingLine { 0% { opacity: 0; width: 0; } 33.3%, 66% { opacity: 0.8; width: 100%; } 85% { width: 0; left: initial; right: 0; opacity: 1; } 100% { opacity: 0; width: 0; } } .loading-container { width:max-content; text-align: center; color: #fff; position: fixed; margin: 0 32px; } .loading-container:before { content: ''; position: absolute; width: max-content; height: 3px; background-color: #fff; bottom: 0; left: 0; border-radius: 10px; animation: movingLine 2.4s infinite ease-in-out; } .loading-text { font-size: 5vw; line-height: 64px; letter-spacing: 10px; margin-bottom: 32px; display: flex; justify-content: space-evenly; } .loading-text span { animation: moveLetters 2.4s infinite ease-in-out; transform: translatex(0); position: relative; display: inline-block; opacity: 0; text-shadow: 0px 2px 10px rgba(46, 74, 81, 0.3); } @for $i from 1 through 7 { .loading-text span:nth-child(#{$i}) { animation-delay: $i * 0.1s; } } @keyframes moveLetters { 0% { transform: translateX(-15vw); opacity: 0; } 33.3%, 66% { transform: translateX(0); opacity: 1; } 100% { transform: translateX(15vw); opacity: 0; } } .socials { position: fixed; bottom: 16px; right: 16px; display: flex; align-items: center; } .social-link { color: #fff; display: flex; align-items: center; cursor: pointer; text-decoration: none; margin-right: 12px; } ``` App.tsx ```typescript= import RenderEditForm from './components/FormEditor'; import FormLinker from './components/FormLinker' import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom'; export default function App() { localStorage.clear(); console.log("CLEAR LocalStorage"); return ( <> <Router> <Switch> <Route path="/edit-autoNA"><FormLinker /></Route> <Route path="/edit-form"><RenderEditForm /></Route> <Route path="/"> <Redirect to="/edit-form" /> </Route> </Switch> </Router> </> ); } ``` ```typescript= import { FieldSrc } from "./FieldSrc"; import { Util } from "./Util"; export class LabelSrc extends FieldSrc{ isTB?: boolean; isBKM?: boolean; isBold?: boolean; isPM50?: boolean; isLink?: boolean; fontWeight?: string; // no use desc?: string; // add props property static props: LabelSrc[] = []; constructor( ){ super(); this.isTB = false; this.isBKM = false; this.colPos = ''; this.rowPos = ''; this.type = Util.C_CELL_TYPE_LABEL; this.fontWeight = ''; this.isPM50 = false; this.isLink = false; this.desc = ''; this.isBold = false; // add this instance to the props array LabelSrc.props.push(this); } public setProps( data: Partial<LabelSrc> ){ // props: Partial<LabelSrc> let getData = LabelSrc.props.find((lastData) => Number(lastData?.rowPos) === Number(data.rowPos) && Number(lastData?.colPos) === Number(data.colPos) ); function padTo2Digits(num: number) { return num.toString().padStart(2, '0'); } function formatDate(date: Date) { return ( [ date.getFullYear(), padTo2Digits(date.getMonth() + 1), padTo2Digits(date.getDate()), padTo2Digits(date.getHours()), padTo2Digits(date.getMinutes()), padTo2Digits(date.getSeconds()), ].join('') ); } if ( getData ) { getData.type = Util.C_CELL_TYPE_LABEL; getData.fieldId = data.fieldId?data.fieldId:getData.fieldId; getData.fieldSeq = data.fieldSeq?data.fieldSeq:getData.fieldSeq; getData.width = data.width?data.width:getData.width; getData.colPos = data.colPos?data.colPos:getData.colPos; getData.rowPos = data.rowPos?data.rowPos:getData.rowPos; getData.fontWeight = data.fontWeight?data.fontWeight:getData.fontWeight; getData.desc = data.desc?data.desc:getData.desc; getData.isChanged = typeof(data.isChanged)==="undefined"?getData.isChanged:data.isChanged; getData.isTB = typeof(data.isTB)==="undefined"?getData.isTB:data.isTB; getData.isBKM = typeof(data.isBKM)==="undefined"?getData.isBKM:data.isBKM; getData.isPM50 = typeof(data.isPM50)==="undefined"?getData.isPM50:data.isPM50; getData.isLink = typeof(data.isLink)==="undefined"?getData.isLink:data.isLink; getData.isBold = typeof(data.isBold)==="undefined"?getData.isBold:data.isBold; getData.totalMergedCells = data.totalMergedCells?data.totalMergedCells:getData.totalMergedCells; } // if else { // First time to create LabelSrc const now = formatDate(new Date()); this.fieldId = data.fieldId?data.fieldId:now+data.rowPos+'-'+data.colPos; this.fieldSeq = data.fieldSeq?data.fieldSeq:this.fieldId; this.isChanged = data.isChanged; this.isTB = data.isTB?data.isTB:false; this.isBKM = data.isBKM?data.isBKM:false; this.colPos = data.colPos; this.rowPos = data.rowPos; this.type = Util.C_CELL_TYPE_LABEL; this.fontWeight = data.fontWeight?data.fontWeight:''; this.isPM50 = data.isPM50?data.isPM50:false; this.isLink = data.isLink?data.isLink:false; this.desc = data.desc?data.desc:''; this.isBold = data.isBold?data.isBold:false; this.totalMergedCells = data.totalMergedCells?data.totalMergedCells:1; this.width = data.width?data.width: "300"; } } public getFieldDataById(fieldId: string) { return LabelSrc.props.find((data) => data?.fieldId === fieldId); } public getFieldDataByPos(row: Number, col: Number) { return LabelSrc.props.find((data) => Number(data?.rowPos) === row && Number(data?.colPos) === col); } } ``` ```typescript= import { FieldSrc } from "./FieldSrc"; import { Util } from "./Util"; export class NumericSrc extends FieldSrc{ decimal?: string; maxValue?: string; minValue?: string; description?: string; isNone?: boolean; isAutoFillECSV?: boolean; isAutoCopyByName?: boolean; // 自動填值 EC/SV source?: string; fillInType?: string; // 一次性/多次性 timing?: string; muliply?: string; isECSVID?: boolean; isECSVName?: boolean; ECSVID?: string; ECSVName?: string; // 自動填本次/前次值 autoFillType?: string; autoFillName?: string; // add props property static props: NumericSrc[] = []; constructor( ){ super(); this.colPos = ''; this.rowPos = ''; this.type = Util.C_CELL_TYPE_NUMINPUT; this.decimal = ''; this.maxValue = ''; this.minValue = ''; this.description = ''; this.isNone = false; // default this.isAutoFillECSV = false; this.isAutoCopyByName = false; this.source = ''; this.fillInType = ''; this.timing = ''; this.muliply = ''; this.ECSVID = ''; this.ECSVName = ''; this.isECSVID = true; // default this.isECSVName = false; this.autoFillType = ''; this.autoFillName = ''; // add this instance to the props array NumericSrc.props.push(this); } public setProps( data: Partial<NumericSrc> ){ // props: Partial<LabelSrc> let getData = NumericSrc.props.find((lastData) => Number(lastData?.rowPos) === Number(data.rowPos) && Number(lastData?.colPos) === Number(data.colPos) ); function padTo2Digits(num: number) { return num.toString().padStart(2, '0'); } function formatDate(date: Date) { return ( [ date.getFullYear(), padTo2Digits(date.getMonth() + 1), padTo2Digits(date.getDate()), padTo2Digits(date.getHours()), padTo2Digits(date.getMinutes()), padTo2Digits(date.getSeconds()), ].join('') ); } if ( getData ) { getData.type = Util.C_CELL_TYPE_NUMINPUT; getData.type = Util.C_CELL_TYPE_NUMINPUT; getData.fieldId = data.fieldId?data.fieldId:getData.fieldId; getData.fieldSeq = data.fieldSeq?data.fieldSeq:getData.fieldSeq; getData.width = data.width?data.width:getData.width; getData.colPos = data.colPos?data.colPos:getData.colPos; getData.rowPos = data.rowPos?data.rowPos:getData.rowPos; getData.isChanged = typeof(data.isChanged)==="undefined"?getData.isChanged:data.isChanged; getData.decimal = data.decimal?data.decimal:getData.decimal; getData.maxValue = data.maxValue?data.maxValue:getData.maxValue; getData.minValue = data.minValue?data.minValue:getData.minValue; getData.description = data.description?data.description:getData.description; getData.isNone = typeof(data.isNone)==="undefined"?getData.isNone:data.isNone; getData.isAutoFillECSV = typeof(data.isAutoFillECSV)==="undefined"?getData.isAutoFillECSV:data.isAutoFillECSV; getData.isAutoCopyByName = typeof(data.isAutoCopyByName)==="undefined"?getData.isAutoCopyByName:data.isAutoCopyByName; getData.source = data.source?data.source:getData.source; getData.fillInType = (getData.source && ( getData.source==='PMS-SV' || getData.source==='RPA'))?"once" :data.fillInType?data.fillInType:getData.fillInType; getData.timing = (getData.source && getData.source==='RPA')?"During PM" :data.timing?data.timing:getData.timing; getData.muliply = data.muliply?data.muliply:getData.muliply; getData.ECSVID = data.ECSVID?data.ECSVID:getData.ECSVID; getData.ECSVName = data.ECSVName?data.ECSVName:getData.ECSVName; getData.isECSVID = typeof(data.isECSVID)==="undefined"?getData.isECSVID:data.isECSVID; getData.isECSVName = typeof(data.isECSVName)==="undefined"?getData.isECSVName:data.isECSVName; getData.autoFillType = data.autoFillType?data.autoFillType:getData.autoFillType; getData.autoFillName = data.autoFillName?data.autoFillName:getData.autoFillName; getData.totalMergedCells = data.totalMergedCells?data.totalMergedCells:getData.totalMergedCells; } // if else { // First time to create NumericSrc const now = formatDate(new Date()); this.fieldId = data.fieldId?data.fieldId:now+data.rowPos+'-'+data.colPos; this.fieldSeq = data.fieldSeq?data.fieldSeq:this.fieldId; this.isChanged = data.isChanged; this.colPos = data.colPos; this.rowPos = data.rowPos; this.type = Util.C_CELL_TYPE_NUMINPUT; this.decimal = data.decimal?data.decimal:''; this.maxValue = data.maxValue?data.maxValue:''; this.minValue = data.minValue?data.minValue:''; this.description = data.description?data.description:''; this.isNone = data.isNone? data.isNone:(data.isAutoFillECSV||data.isAutoCopyByName)?false:true; this.isAutoFillECSV = data.isAutoFillECSV?data.isAutoFillECSV:false; this.isAutoCopyByName = data.isAutoCopyByName?data.isAutoCopyByName:false; this.source = data.source?data.source:''; this.fillInType = (this.source && ( this.source==='PMS-SV' || this.source==='RPA'))?"once" :data.fillInType?data.fillInType:this.fillInType; this.timing = (this.source && this.source==='RPA')?"During PM" :data.timing?data.timing:''; this.muliply = data.muliply?data.muliply:''; this.ECSVID = data.ECSVID?data.ECSVID:''; this.ECSVName = data.ECSVName?data.ECSVName:''; this.isECSVID = data.isECSVID?data.isECSVID:(data.isECSVName)?false:true; // default this.isECSVName = data.isECSVName?data.isECSVName:false; this.totalMergedCells = data.totalMergedCells?data.totalMergedCells:1; this.width = data.width?data.width:"300"; } // else } public getFieldDataById(fieldId: string) { return NumericSrc.props.find((data) => data?.fieldId === fieldId); } public getFieldDataByPos(row: Number, col: Number) { return NumericSrc.props.find((data) => Number(data?.rowPos) === row && Number(data?.colPos) === col); } } ``` ```typescript= import { FieldSrc } from "./FieldSrc"; import { Util } from "./Util"; interface RadioButton { sequence: string; desc: string; } export class RadioButtonSrc extends FieldSrc{ layout?: string; // horizontal or vertical template?: string; // none, v_x, v_x_na radioButtonGroup?: RadioButton[]; selectedRadioBtn?: string; // add props property static props: RadioButtonSrc[] = []; constructor( ){ super(); this.width = ''; this.colPos = ''; this.rowPos = ''; this.type = Util.C_CELL_TYPE_RADIOBUTTON; this.layout = ''; this.template = ''; this.radioButtonGroup = []; this.selectedRadioBtn = ''; // add this instance to the props array RadioButtonSrc.props.push(this); } public setProps( data: Partial<RadioButtonSrc> ){ // props: Partial<LabelSrc> let getData = RadioButtonSrc.props.find((lastData) => Number(lastData?.rowPos) === Number(data.rowPos) && Number(lastData?.colPos) === Number(data.colPos) ); function padTo2Digits(num: number) { return num.toString().padStart(2, '0'); } function formatDate(date: Date) { return ( [ date.getFullYear(), padTo2Digits(date.getMonth() + 1), padTo2Digits(date.getDate()), padTo2Digits(date.getHours()), padTo2Digits(date.getMinutes()), padTo2Digits(date.getSeconds()), ].join('') ); } if ( getData ) { getData.type = Util.C_CELL_TYPE_RADIOBUTTON; getData.fieldId = data.fieldId?data.fieldId:getData.fieldId; getData.fieldSeq = data.fieldSeq?data.fieldSeq:getData.fieldSeq; getData.width = data.width?data.width:getData.width; getData.colPos = data.colPos?data.colPos:getData.colPos; getData.rowPos = data.rowPos?data.rowPos:getData.rowPos; getData.isChanged = typeof(data.isChanged)==="undefined"?getData.isChanged:data.isChanged; getData.layout = data.layout?data.layout:getData.layout; getData.template = data.template?data.template:getData.template; getData.radioButtonGroup = data.radioButtonGroup?data.radioButtonGroup:getData.radioButtonGroup; getData.selectedRadioBtn = data.selectedRadioBtn?data.selectedRadioBtn:getData.selectedRadioBtn; getData.totalMergedCells = data.totalMergedCells?data.totalMergedCells:getData.totalMergedCells; } // if else { // First time to create RadioButtonSrc const now = formatDate(new Date()); this.fieldId = data.fieldId?data.fieldId:now+data.rowPos+'-'+data.colPos; this.fieldSeq = data.fieldSeq?data.fieldSeq:this.fieldId; this.isChanged = data.isChanged?data.isChanged:false; this.colPos = data.colPos; this.rowPos = data.rowPos; this.type = Util.C_CELL_TYPE_RADIOBUTTON; this.layout = data.layout?data.layout:'horizontal'; // default : horizontal this.template = data.template?data.template:'none'; // default : none this.radioButtonGroup = data.radioButtonGroup; this.selectedRadioBtn = data.selectedRadioBtn?data.selectedRadioBtn:''; this.width = data.width?data.width:"300"; this.totalMergedCells = data.totalMergedCells?data.totalMergedCells:1; } // else } public getFieldDataById(fieldId: string) { return RadioButtonSrc.props.find((data) => data?.fieldId === fieldId); } public getFieldDataByPos(row: Number, col: Number) { return RadioButtonSrc.props.find((data) => Number(data?.rowPos) === row && Number(data?.colPos) === col); } } ``` ```typescript= import { FieldSrc } from "./FieldSrc"; import { Util } from "./Util"; export class TextSrc extends FieldSrc{ length?: string; // frontend custom isNone?: boolean; isBarcode?: boolean; isAutoToolId?: boolean; isAutoPMStartTime?: boolean; isJig?: boolean; desc?: string; // [Barcode field] naRfCol?: string; partNum?: string; inChLoc?: string; // add props property static props: TextSrc[] = []; constructor( ){ super(); this.type = Util.C_CELL_TYPE_TEXTINPUT; this.length = ''; this.isNone = false; this.isBarcode = false; this.isAutoToolId = false; this.isAutoPMStartTime = false; this.isJig = false; this.desc = ''; // [Barcode field] this.naRfCol = ''; this.partNum = ''; this.inChLoc = ''; // add this instance to the props array TextSrc.props.push(this); } public setProps( data: Partial<TextSrc> ){ let getData = TextSrc.props.find((lastData) => Number(lastData?.rowPos) === Number(data.rowPos) && Number(lastData?.colPos) === Number(data.colPos) ); function padTo2Digits(num: number) { return num.toString().padStart(2, '0'); } function formatDate(date: Date) { return ( [ date.getFullYear(), padTo2Digits(date.getMonth() + 1), padTo2Digits(date.getDate()), padTo2Digits(date.getHours()), padTo2Digits(date.getMinutes()), padTo2Digits(date.getSeconds()), ].join('') ); } if ( getData ) { getData.type = Util.C_CELL_TYPE_TEXTINPUT; getData.fieldId = data.fieldId?data.fieldId:getData.fieldId; getData.fieldSeq = data.fieldSeq?data.fieldSeq:getData.fieldSeq; getData.width = data.width?data.width:getData.width; getData.colPos = data.colPos?data.colPos:getData.colPos; getData.rowPos = data.rowPos?data.rowPos:getData.rowPos; getData.isChanged = typeof(data.isChanged)==="undefined"?getData.isChanged:data.isChanged; getData.isNone = typeof(data.isNone)==="undefined"?getData.isNone:data.isNone; getData.isBarcode = typeof(data.isBarcode)==="undefined"?getData.isBarcode:data.isBarcode; getData.isAutoToolId = typeof(data.isAutoToolId)==="undefined"?getData.isAutoToolId:data.isAutoToolId; getData.isAutoPMStartTime = typeof(data.isAutoPMStartTime)==="undefined"?getData.isAutoPMStartTime:data.isAutoPMStartTime; getData.isJig = typeof(data.isJig)==="undefined"?getData.isJig:data.isJig; getData.desc = data.desc?data.desc:getData.desc; getData.naRfCol = data.naRfCol?data.naRfCol:getData.naRfCol; getData.partNum = data.partNum?data.partNum:getData.partNum; getData.inChLoc = data.inChLoc?data.inChLoc:getData.inChLoc; getData.totalMergedCells = data.totalMergedCells?data.totalMergedCells:getData.totalMergedCells; getData.length = data.length?data.length:getData.length; } // if else { // First time to create TextSrc const now = formatDate(new Date()); this.fieldId = data.fieldId?data.fieldId:now+data.rowPos+'-'+data.colPos; this.fieldSeq = data.fieldSeq?data.fieldSeq:this.fieldId; this.isChanged = data.isChanged?data.isChanged:false; this.colPos = data.colPos; this.rowPos = data.rowPos; this.type = Util.C_CELL_TYPE_TEXTINPUT; // [default value] this.isNone = data.isNone? data.isNone: (data.isBarcode||data.isAutoToolId||data.isAutoPMStartTime||data.isJig)?false: true; this.isBarcode = data.isBarcode? data.isBarcode: false; this.isAutoToolId = data.isAutoToolId? data.isAutoToolId: false; this.isAutoPMStartTime = data.isAutoPMStartTime? data.isAutoPMStartTime: false; this.isJig = data.isJig? data.isJig: false; this.desc = data.desc?data.desc:'[text](na)'; // default value // [Barcode field] this.naRfCol = data.naRfCol?data.naRfCol:''; this.partNum = data.partNum?data.partNum:''; this.inChLoc = data.inChLoc?data.inChLoc:''; this.totalMergedCells = data.totalMergedCells?data.totalMergedCells:1; this.width = data.width?data.width:"300"; this.length = data.length?data.length:''; } // else } public getFieldDataById(fieldId: string) { return TextSrc.props.find((data) => data?.fieldId === fieldId); } public getFieldDataByPos(row: Number, col: Number) { return TextSrc.props.find((data) => Number(data?.rowPos) === row && Number(data?.colPos) === col); } } ```