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);
}
}
```