Open kirin-ri opened 1 year ago
この画面にポップアップを追加する。最終から出てくる感じで。ポップアップに「test」を表示。OKボタンで閉じれるように
import * as Defs from './metricsDefs';
function MetricsIfInfoModal({details}: {details: Defs.MetricsDetails}) {
return (
<div className="modal fade" id="metricsIfInfo">
<div className="modal-dialog modal-xl">
<div className="modal-content">
<div className="modal-header">
<div className="ref-data-label">
参照データ
</div>
<div className="metrics-title">
{details.id}
</div>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<div className="card card-secondary in-data">
{/* <!-- title row --> */}
<div className="card-header">
<h3 className="card-title">INデータ項目</h3>
</div>
<div className="card-body">
{details?.in.map((table, idx) =>
<IfData table={table} key={idx}/>)}
</div>
</div>
{/* <!-- /.invoice --> */}
<div className="card card-secondary out-data">
{/* <!-- title row --> */}
<div className="card-header">
<h3 className="card-title">OUTデータ項目</h3>
</div>
<div className="card-body">
{details?.out.map((table, idx) =>
<IfData table={table} key={idx}/>)}
</div>
</div>
{/* <!-- /.invoice --> */}
<div className="card card-secondary">
<div className="card-header">
<h3 className="card-title">API</h3>
</div>
<div className="card-body">
<div className="table-responsive">
<Api details={details}/>
</div>
</div>
</div>
<div className="modal-footer">
<button className="btn btn-primary" data-dismiss="modal">閉じる</button>
</div>
</div>
{/* <!-- /.modal-body --> */}
</div>
{/* <!-- /.modal-content --> */}
</div>
{/* <!-- /.modal-dialog --> */}
</div>
)
}
function IfData({table}: {table: Defs.TableInfo}) {
return (
<div>
<div className="table-info-header">
<div className="table-info-header-label">
DB
</div>
<div className="table-info-header-value">
{table.db}
</div>
<div className="table-info-header-label">
Schema
</div>
<div className="table-info-header-value">
{table.schema}
</div>
<div className="table-info-header-label">
Table
</div>
<div className="table-info-header-value">
{table.table}
</div>
</div>
{/* <!-- Table row --> */}
<div className="row">
<div className="col-12 table-responsive">
<table className="table table-striped">
<thead>
<tr>
<th>論理名</th>
<th>属性</th>
<th>物理名</th>
</tr>
</thead>
<tbody>
{table.column.map((colum, idx) => {
return (
<tr key={idx}>
<td>{colum.logicalName}</td>
<td>{colum.type}</td>
<td>{colum.physicalName}</td>
</tr>
);
})}
</tbody>
</table>
</div>
{/* <!-- /.col --> */}
</div>
{/* <!-- /.row --> */}
</div>
);
}
function Api({details}: {details: Defs.MetricsDetails}) {
return (
<table className="table table-striped">
<thead>
<tr>
<th>名称</th>
</tr>
</thead>
<tbody>
{details?.api.map((name, idx) => {
return (
<tr key={idx}>
<td>{name}</td>
</tr>
);
})}
</tbody>
</table>
)
}
export default MetricsIfInfoModal;
お知らせメッセージを表示する。
「OK」ボタンを押下、ポップアップが閉じる。
ーーーーーーーーーーーーーーーーーーーー\| API項目が更新されました。 \| \| 再度マッピングを行う必要があります。 \|\| \| \| [OK] \| ーーーーーーーーーーーーーーーーーーーー --案1
お知らせメッセージを表示する。
「OK」ボタンを押下、ポップアップが閉じる。
案2
メッセージおよび関連するメトリクス名を表示する。
「OK」ボタンを押下、ポップアップが閉じる。
案3
メッセージおよび関連するメトリクス名を表示する。
メトリクス名をクリックし、各改修画面に遷移する。
※関連メトリクスが複数ある場合、一個目修正後戻れない?
import { commonAjax } from '../../../components/commonAjax'; import React, { useState, useEffect } from 'react'; import parse from 'html-react-parser'; import { useHistory } from 'react-router-dom'; import { Spacer } from '../spacer'; import * as Defs from './metricsDefs'; import MetricsIfInfoModal from './metricsIfInfoModal'
interface MyProps { match: { params: { id: string; }; }; }
function AppDashboard(props: MyProps) {
const [metricsId, setMetricsId] = useState
/ リクエスト /
useEffect(() => {
setMetricsDetails(null);
commonAjax
.axios({loading: true})
.get(/api/metrics/${props.match.params.id}
)
.then((res) => {
const details = res.data;
setMetricsDetails(details);
/ setMetricsDetails(getSampleData4()); /
const metrics = Defs.getMetrics(details);
setMetrics(metrics);
setEnabledStats(metrics?.getStatValues());
setDeployBtnDisabled(true);
});
}, [props.match.params.id, history]);
const handleCycleClick = (metricsName: string) => {
setMetricsId(metricsName);
history.push(/scm-metrics-details/${metricsName}
);
};
const handleStatClick = (stat: Defs.StatDef) => {
if (enabledStats.includes(stat)) {
// ON -> OFF
setEnabledStats(enabledStats.filter(s => s !== stat));
} else {
// OFF -> ON
setEnabledStats([...enabledStats, stat]);
}
}
const handleDeployMetrics = async () => { setDeployBtnDisabled(false);
commonAjax
.axios({})
.put(`/api/metrics/deploy/${metricsId}`)
.then((res) => {
setDeployMetrics(res.data.message);
})
.catch((err) => {
console.log(err);
});
return;
};
return (
)}
</section>
<section className="content-footer">
<div className="content-footer-left">
</div>
<div className="content-footer-right">
<button className="btn btn-primary" data-toggle="modal" data-target="#metricsIfInfo">参照元データを確認する</button>
</div>
</section>
{metricsDetails && <MetricsIfInfoModal details={metricsDetails}/>}
</div>
); }
function FilterTags({details, metrics, enabledStats, onCycleClick, onStatClick}: {
details: Defs.MetricsDetails, metrics: Defs.Metrics, enabledStats: Defs.StatDef[], onCycleClick:(id: string) => void, onStatClick:(stat: Defs.StatDef) => void}) {
const cycles = details.cycles.map(({cycle, id}) =>
cycle && <button className={btn btn-tag${id === details.id ? " active" : ""}
}
onClick={() => onCycleClick(id)} key={id}>{cycle}
);
const stats = metrics?.getStatValues().map(s =>
<button className={btn btn-tag${enabledStats?.includes(s) ? " active" : ""}
}
onClick={() => onStatClick(s)} key={s.id}>{s.display}
);
return (
) }
function Chart({details, metrics, enabledStats, disableButton, onDeployMetrics}: { details: Defs.MetricsDetails, metrics: Defs.Metrics, enabledStats: Defs.StatDef[], disableButton: boolean, onDeployMetrics: () => void}) {
if (details) { if (!details.provide) { return (
);
}
if (metrics) {
return metrics.render(details, enabledStats);
}
} return
; };function MetricsProcessing({details}: {details: Defs.MetricsDetails}) { const html = (details?.processing || "") .replaceAll(/(\r?\n)?<hr\/?>(\r?\n)?/g, "
import { commonAjax } from '../../../components/commonAjax';
import React, { useState, useEffect } from 'react';
import parse from 'html-react-parser';
import { useHistory } from 'react-router-dom';
import { Spacer } from '../spacer';
import * as Defs from './metricsDefs';
import MetricsIfInfoModal from './metricsIfInfoModal'
interface MyProps {
match: {
params: {
id: string;
};
};
}
function AppDashboard(props: MyProps) {
const [metricsId, setMetricsId] = useState<string>(props.match.params.id);
const [metricsDetails, setMetricsDetails] = useState<Defs.MetricsDetails|null>(null);
const history = useHistory();
const [deployMetrics, setDeployMetrics] = useState<string>('');
const [enabledStats, setEnabledStats] = useState<Defs.StatDef[]>([]);
const [isDeployBtnDisabled, setDeployBtnDisabled] = useState<boolean>(true);
const [metrics, setMetrics] = useState<Defs.Metrics>();
/* リクエスト */
useEffect(() => {
setMetricsDetails(null);
commonAjax
.axios({loading: true})
.get(`/api/metrics/${props.match.params.id}`)
.then((res) => {
const details = res.data;
setMetricsDetails(details);
/* setMetricsDetails(getSampleData4()); */
const metrics = Defs.getMetrics(details);
setMetrics(metrics);
setEnabledStats(metrics?.getStatValues());
setDeployBtnDisabled(true);
});
}, [props.match.params.id, history]);
const handleCycleClick = (metricsName: string) => {
setMetricsId(metricsName);
history.push(`/scm-metrics-details/${metricsName}`);
};
const handleStatClick = (stat: Defs.StatDef) => {
if (enabledStats.includes(stat)) {
// ON -> OFF
setEnabledStats(enabledStats.filter(s => s !== stat));
} else {
// OFF -> ON
setEnabledStats([...enabledStats, stat]);
}
}
const handleDeployMetrics = async () => {
setDeployBtnDisabled(false);
commonAjax
.axios({})
.put(`/api/metrics/deploy/${metricsId}`)
.then((res) => {
setDeployMetrics(res.data.message);
})
.catch((err) => {
console.log(err);
});
return;
};
return (
<div className="content-wrapper metrics-details">
<section className="page-cover">
<div className="page-cover-title-frame">
<h1>{metricsDetails?.category}</h1>
<div>{metricsDetails?.id}</div>
</div>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
{metricsDetails && <>
<h1 className="metrics-cat">{metricsDetails?.category}</h1>
<h1 className="metrics-id">{metricsDetails?.id}</h1>
</>}
</div>
<div className="content-header-right">
{provided(metricsDetails?.provide || false)}
<a className="btn btn-secondary" href={`#/scm-metrics-edit/${metricsDetails?.id}`}>データを編集する</a>
</div>
</section>
{metricsDetails && metrics &&
<FilterTags details={metricsDetails} metrics={metrics} enabledStats={enabledStats} onCycleClick={handleCycleClick} onStatClick={handleStatClick}/>}
<section className="content">
<div className="metrics-header">
<div className="metrics-title">
{metricsDetails?.id}
</div>
<div className="metrics-desc">
{metricsDetails?.description}
</div>
</div>
{metricsDetails && metrics && (
<div className={`chart-content${metrics instanceof Defs.MetricsTable ? " chart-content-table" : ""}`}>
<MetricsProcessing details={metricsDetails}/>
<div className="charts">
<Chart details={metricsDetails} metrics={metrics} enabledStats={enabledStats} disableButton={isDeployBtnDisabled} onDeployMetrics={handleDeployMetrics}/>
</div>
</div>
)}
</section>
<section className="content-footer">
<div className="content-footer-left">
</div>
<div className="content-footer-right">
<button className="btn btn-primary" data-toggle="modal" data-target="#metricsIfInfo">参照元データを確認する</button>
</div>
</section>
{metricsDetails && <MetricsIfInfoModal details={metricsDetails}/>}
</div>
);
}
function FilterTags({details, metrics, enabledStats, onCycleClick, onStatClick}: {
details: Defs.MetricsDetails, metrics: Defs.Metrics, enabledStats: Defs.StatDef[], onCycleClick:(id: string) => void, onStatClick:(stat: Defs.StatDef) => void}) {
const cycles = details.cycles.map(({cycle, id}) =>
cycle && <button className={`btn btn-tag${id === details.id ? " active" : ""}`}
onClick={() => onCycleClick(id)} key={id}>{cycle}</button>
);
const stats = metrics?.getStatValues().map(s =>
<button className={`btn btn-tag${enabledStats?.includes(s) ? " active" : ""}`}
onClick={() => onStatClick(s)} key={s.id}>{s.display}</button>
);
return (
<section className="content-header control-panel">
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
<div className="inline-form-group">
<div className="inline-form-label">表示周期</div>
{cycles}
</div>
<div className="inline-form-group">
<div className="inline-form-label">表示値</div>
{stats}
</div>
</div>
</section>
)
}
function Chart({details, metrics, enabledStats, disableButton, onDeployMetrics}: {
details: Defs.MetricsDetails, metrics: Defs.Metrics, enabledStats: Defs.StatDef[], disableButton: boolean, onDeployMetrics: () => void}) {
if (details) {
if (!details.provide) {
return (
<div className="deploy-chart">
<Spacer size={100} />
<div className="text-center">
<button
className={
disableButton
? 'btn btn-primary'
: 'btn btn-primary disabled'
}
onClick={onDeployMetrics}
>
Deploy
</button>
</div>
<Spacer size={100} />
</div>
);
}
if (metrics) {
return metrics.render(details, enabledStats);
}
}
return <div></div>;
};
function MetricsProcessing({details}: {details: Defs.MetricsDetails}) {
const html = (details?.processing || "")
.replaceAll(/(\r?\n)?<hr\/?>(\r?\n)?/g, "<hr/>")
.split(/(\r?\n)/)
.map((line, idx) => (
<React.Fragment key={idx}>{line.match(/\r?\n/) ? <br/> : parse(line)}</React.Fragment>
));
return (
<div className="card card-metrics-proc">
<div className="card-header">メトリクス算出方法</div>
<div className="card-body">
{html}
</div>
</div>
)
}
/* 提供済みフラグ */
function provided(isProvided: boolean) {
if (isProvided) {
return <div><i className="fa fa-circle text-success" aria-hidden="true" /> provided</div>;
}
return <div><i className="fa fa-circle text-danger" aria-hidden="true" /> provided</div>;
}
export default AppDashboard;
const getSampleData = () => {
return {
id: '部品納期管理/納期遵守率(ETD):月次',
category: '部品納期管理',
description:
'サプライヤの部品納期回答精度(月次)として、遅延日数の平均値、最大値、最小値、中央値、標準偏差を算出',
provide: true,
tags: ['SCM', '部品納期管理', '納期遵守率', '月次'],
lastUpdate: '2022/12/1',
version: 'v1.0.0',
in: [
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PRIMARYSUPPLIER_PRIMARYPART_SHIPMENT',
column: [
{
logicalName: 'サプライヤ名',
physicalName: 'SUPPLIER_NAME',
type: '文字列',
},
{
logicalName: '工場名',
physicalName: 'FACTORY_NAME',
type: '文字列',
},
],
},
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PRIMARYPART_ORDERPLAN_REPLY',
column: [
{
logicalName: '基準日',
physicalName: 'REF_DATE',
type: '日付',
},
],
},
],
out: [
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PARTSDELIVERY_ONTIMEDELIVERYRATE_MONTHLY_DELAYDAYS',
column: [
{
logicalName: 'サプライヤ名',
physicalName: 'SUPPLIER_NAME',
type: '文字列',
},
{
logicalName: '工場名',
physicalName: 'FACTORY_NAME',
type: '文字列',
},
{
logicalName: '基準日',
physicalName: 'REF_DATE',
type: '日付',
},
],
},
],
api: [
'PRIMARYSUPPLIER_PRIMARYPART_SHIPMENT',
'PRIMARYPART_ORDERPLAN_REPLY',
],
metrics_pattern: 'max,min,avg,median,stddev',
graph_title: ['平均値、最大値、最小値、中央値', '平均値 ± 標準偏差'],
data: [
{
label: 'Tier1-C factory A',
date: '2022-10',
max: 11,
min: 3,
avg: 6,
median: 4,
stddev: 4.358898944,
},
{
label: 'Tier1-B factory B',
date: '2022-10',
max: 10,
min: 10,
avg: 10,
median: 10,
stddev: 0,
},
{
label: 'Tier1-A factory A',
date: '2022-10',
max: 10,
min: 0,
avg: 4.25,
median: 3.5,
stddev: 5.057996968,
},
{
label: 'Tier1-A factory B',
date: '2022-10',
max: 10,
min: 7,
avg: 8.5,
median: 8.5,
stddev: 2.121320344,
},
{
label: 'Tier1-C factory B',
date: '2022-10',
max: 11,
min: 11,
avg: 11,
median: 11,
stddev: 0,
},
{
label: 'Tier1-B factory A',
date: '2022-10',
max: 10,
min: 0,
avg: 5,
median: 5,
stddev: 7.071067812,
},
{
label: 'Tier1-C factory A',
date: '2022-11',
max: 11,
min: 3,
avg: 6,
median: 4,
stddev: 4.358898944,
},
{
label: 'Tier1-B factory B',
date: '2022-11',
max: 10,
min: 10,
avg: 10,
median: 10,
stddev: 0,
},
{
label: 'Tier1-A factory A',
date: '2022-11',
max: 10,
min: 0,
avg: 4.25,
median: 3.5,
stddev: 5.057996968,
},
{
label: 'Tier1-A factory B',
date: '2022-11',
max: 10,
min: 7,
avg: 8.5,
median: 8.5,
stddev: 2.121320344,
},
{
label: 'Tier1-C factory B',
date: '2022-11',
max: 11,
min: 11,
avg: 11,
median: 11,
stddev: 0,
},
{
label: 'Tier1-B factory A',
date: '2022-11',
max: 10,
min: 0,
avg: 5,
median: 5,
stddev: 7.071067812,
},
{
label: 'Tier1-C factory A',
date: '2022-12',
max: 11,
min: 3,
avg: 6,
median: 4,
stddev: 4.358898944,
},
{
label: 'Tier1-B factory B',
date: '2022-12',
max: 10,
min: 10,
avg: 10,
median: 10,
stddev: 0,
},
{
label: 'Tier1-A factory A',
date: '2022-12',
max: 10,
min: 0,
avg: 4.25,
median: 3.5,
stddev: 5.057996968,
},
{
label: 'Tier1-A factory B',
date: '2022-12',
max: 10,
min: 7,
avg: 8.5,
median: 8.5,
stddev: 2.121320344,
},
{
label: 'Tier1-C factory B',
date: '2022-12',
max: 11,
min: 11,
avg: 11,
median: 11,
stddev: 0,
},
{
label: 'Tier1-B factory A',
date: '2022-12',
max: 10,
min: 0,
avg: 5,
median: 5,
stddev: 7.071067812,
},
],
};
};
const getSampleData1 = () => {
return {
id: '部品納期管理/納期遵守率(ETD):月次',
category: '部品納期管理',
description:
'サプライヤの部品納期回答精度(月次)として、遅延日数の平均値、最大値、最小値、中央値、標準偏差を算出',
provide: false,
tags: ['SCM', '部品納期管理', '納期遵守率', '月次'],
lastUpdate: '2022/12/1',
version: 'v1.0.0',
in: [
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
name: 'PRIMARYSUPPLIER_PRIMARYPART_SHIPMENT',
column: [
{
logicalName: 'サプライヤ名',
physicalName: 'SUPPLIER_NAME',
type: '文字列',
},
{
logicalName: '工場名',
physicalName: 'FACTORY_NAME',
type: '文字列',
},
],
},
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
name: 'PRIMARYPART_ORDERPLAN_REPLY',
column: [
{
logicalName: '基準日',
physicalName: 'REF_DATE',
type: '日付',
},
],
},
],
out: [
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
name: 'PARTSDELIVERY_ONTIMEDELIVERYRATE_MONTHLY_DELAYDAYS',
column: [
{
logicalName: 'サプライヤ名',
physicalName: 'SUPPLIER_NAME',
type: '文字列',
},
{
logicalName: '工場名',
physicalName: 'FACTORY_NAME',
type: '文字列',
},
{
logicalName: '基準日',
physicalName: 'REF_DATE',
type: '日付',
},
],
},
],
api: [
'PRIMARYSUPPLIER_PRIMARYPART_SHIPMENT',
'PRIMARYPART_ORDERPLAN_REPLY',
],
metrics_pattern: 'max,min,avg,median,stddev',
graph_title: ['平均値、最大値、最小値、中央値', '平均値 ± 標準偏差'],
data: [
{
label: 'Tier1-C factory A',
date: '2022-10',
max: 11,
min: 3,
avg: 6,
median: 4,
stddev: 4.358898944,
},
{
label: 'Tier1-B factory B',
date: '2022-10',
max: 10,
min: 10,
avg: 10,
median: 10,
stddev: 0,
},
{
label: 'Tier1-A factory A',
date: '2022-10',
max: 10,
min: 0,
avg: 4.25,
median: 3.5,
stddev: 5.057996968,
},
{
label: 'Tier1-A factory B',
date: '2022-10',
max: 10,
min: 7,
avg: 8.5,
median: 8.5,
stddev: 2.121320344,
},
{
label: 'Tier1-C factory B',
date: '2022-10',
max: 11,
min: 11,
avg: 11,
median: 11,
stddev: 0,
},
{
label: 'Tier1-B factory A',
date: '2022-10',
max: 10,
min: 0,
avg: 5,
median: 5,
stddev: 7.071067812,
},
{
label: 'Tier1-C factory A',
date: '2022-11',
max: 11,
min: 3,
avg: 6,
median: 4,
stddev: 4.358898944,
},
{
label: 'Tier1-B factory B',
date: '2022-11',
max: 10,
min: 10,
avg: 10,
median: 10,
stddev: 0,
},
{
label: 'Tier1-A factory A',
date: '2022-11',
max: 10,
min: 0,
avg: 4.25,
median: 3.5,
stddev: 5.057996968,
},
{
label: 'Tier1-A factory B',
date: '2022-11',
max: 10,
min: 7,
avg: 8.5,
median: 8.5,
stddev: 2.121320344,
},
{
label: 'Tier1-C factory B',
date: '2022-11',
max: 11,
min: 11,
avg: 11,
median: 11,
stddev: 0,
},
{
label: 'Tier1-B factory A',
date: '2022-11',
max: 10,
min: 0,
avg: 5,
median: 5,
stddev: 7.071067812,
},
{
label: 'Tier1-C factory A',
date: '2022-12',
max: 11,
min: 3,
avg: 6,
median: 4,
stddev: 4.358898944,
},
{
label: 'Tier1-B factory B',
date: '2022-12',
max: 10,
min: 10,
avg: 10,
median: 10,
stddev: 0,
},
{
label: 'Tier1-A factory A',
date: '2022-12',
max: 10,
min: 0,
avg: 4.25,
median: 3.5,
stddev: 5.057996968,
},
{
label: 'Tier1-A factory B',
date: '2022-12',
max: 10,
min: 7,
avg: 8.5,
median: 8.5,
stddev: 2.121320344,
},
{
label: 'Tier1-C factory B',
date: '2022-12',
max: 11,
min: 11,
avg: 11,
median: 11,
stddev: 0,
},
{
label: 'Tier1-B factory A',
date: '2022-12',
max: 10,
min: 0,
avg: 5,
median: 5,
stddev: 7.071067812,
},
],
};
};
const getSampleData2 = () => {
return {
id: '部品納期管理/納期遵守率(ETD):月次',
category: '部品納期管理',
description:
'サプライヤの部品納期回答精度(月次)として、遅延日数の平均値、最大値、最小値、中央値、標準偏差を算出',
provide: true,
tags: ['SCM', '部品納期管理', '納期遵守率', '月次'],
lastUpdate: '2022/12/1',
version: 'v1.0.0',
in: [
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PRIMARYSUPPLIER_PRIMARYPART_SHIPMENT',
column: [
{
logicalName: 'サプライヤ名',
physicalName: 'SUPPLIER_NAME',
type: '文字列',
},
{
logicalName: '工場名',
physicalName: 'FACTORY_NAME',
type: '文字列',
},
],
},
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PRIMARYPART_ORDERPLAN_REPLY',
column: [
{
logicalName: '基準日',
physicalName: 'REF_DATE',
type: '日付',
},
],
},
],
out: [
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PARTSDELIVERY_ONTIMEDELIVERYRATE_MONTHLY_DELAYDAYS',
column: [
{
logicalName: 'サプライヤ名',
physicalName: 'SUPPLIER_NAME',
type: '文字列',
},
{
logicalName: '工場名',
physicalName: 'FACTORY_NAME',
type: '文字列',
},
{
logicalName: '基準日',
physicalName: 'REF_DATE',
type: '日付',
},
],
},
],
api: [
'PRIMARYSUPPLIER_PRIMARYPART_SHIPMENT',
'PRIMARYPART_ORDERPLAN_REPLY',
],
metrics_pattern: 'rate',
graph_title: ['供給率'],
data: [
{
label: 'Tier1-C factory A',
date: '2022/11/6',
rate: 0.7,
},
{
label: 'Tier1-B factory B',
date: '2022/11/6',
rate: 0.8,
},
{
label: 'Tier1-A factory A',
date: '2022/11/6',
rate: 0.6,
},
{
label: 'Tier1-A factory B',
date: '2022/11/6',
rate: 0.4,
},
{
label: 'Tier1-C factory B',
date: '2022/11/6',
rate: 0.6,
},
{
label: 'Tier1-B factory A',
date: '2022/11/6',
rate: 0.3,
},
{
label: 'Tier1-C factory A',
date: '2022/11/13',
rate: 0.2,
},
{
label: 'Tier1-B factory B',
date: '2022/11/13',
rate: 0.9,
},
{
label: 'Tier1-A factory A',
date: '2022/11/13',
rate: 1,
},
{
label: 'Tier1-A factory B',
date: '2022/11/13',
rate: 0.2,
},
{
label: 'Tier1-C factory B',
date: '2022/11/13',
rate: 0.3,
},
{
label: 'Tier1-B factory A',
date: '2022/11/13',
rate: 0.44,
},
{
label: 'Tier1-C factory A',
date: '2022/11/20',
rate: 0.55,
},
{
label: 'Tier1-B factory B',
date: '2022/11/20',
rate: 0.25,
},
{
label: 'Tier1-A factory A',
date: '2022/11/20',
rate: 0.8,
},
{
label: 'Tier1-A factory B',
date: '2022/11/20',
rate: 0.8,
},
{
label: 'Tier1-C factory B',
date: '2022/11/20',
rate: 0.8,
},
{
label: 'Tier1-B factory A',
date: '2022/11/20',
rate: 0.8,
},
],
};
};
const getSampleData3 = () => {
return {
id: '部品納期管理/納期遵守率(ETD):月次',
category: '部品納期管理',
description:
'サプライヤの部品納期回答精度(月次)として、遅延日数の平均値、最大値、最小値、中央値、標準偏差を算出',
provide: true,
tags: ['SCM', '部品納期管理', '納期遵守率', '月次'],
lastUpdate: '2022/12/1',
version: 'v1.0.0',
in: [
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PRIMARYSUPPLIER_PRIMARYPART_SHIPMENT',
column: [
{
logicalName: 'サプライヤ名',
physicalName: 'SUPPLIER_NAME',
type: '文字列',
},
{
logicalName: '工場名',
physicalName: 'FACTORY_NAME',
type: '文字列',
},
],
},
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PRIMARYPART_ORDERPLAN_REPLY',
column: [
{
logicalName: '基準日',
physicalName: 'REF_DATE',
type: '日付',
},
],
},
],
out: [
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PARTSDELIVERY_ONTIMEDELIVERYRATE_MONTHLY_DELAYDAYS',
column: [
{
logicalName: 'サプライヤ名',
physicalName: 'SUPPLIER_NAME',
type: '文字列',
},
{
logicalName: '工場名',
physicalName: 'FACTORY_NAME',
type: '文字列',
},
{
logicalName: '基準日',
physicalName: 'REF_DATE',
type: '日付',
},
],
},
],
api: [
'PRIMARYSUPPLIER_PRIMARYPART_SHIPMENT',
'PRIMARYPART_ORDERPLAN_REPLY',
],
metrics_pattern: 'progress',
graph_title: ['生産進度'],
data: [
{
label: 'Tier1-A factory A',
date: '2022/11/6',
progress: 0,
},
{
label: 'Tier1-A factory A',
date: '2022/11/7',
progress: 0.2,
},
{
label: 'Tier1-A factory A',
date: '2022/11/8',
progress: 0.45,
},
{
label: 'Tier1-A factory A',
date: '2022/11/9',
progress: 0.55,
},
{
label: 'Tier1-A factory A',
date: '2022/11/10',
progress: 0.8,
},
{
label: 'Tier1-A factory A',
date: '2022/11/11',
progress: 0.9,
},
{
label: 'Tier1-A factory A',
date: '2022/11/12',
progress: 1,
},
{
label: 'Tier1-A factory B',
date: '2022/11/6',
progress: 0.1,
},
{
label: 'Tier1-A factory B',
date: '2022/11/7',
progress: 0.3,
},
{
label: 'Tier1-A factory B',
date: '2022/11/8',
progress: 0.45,
},
{
label: 'Tier1-A factory B',
date: '2022/11/9',
progress: 0.65,
},
{
label: 'Tier1-A factory B',
date: '2022/11/10',
progress: 0.7,
},
{
label: 'Tier1-A factory B',
date: '2022/11/11',
progress: 0.8,
},
{
label: 'Tier1-A factory B',
date: '2022/11/12',
progress: 0.9,
},
{
label: 'Tier1-A factory C',
date: '2022/11/6',
progress: 0.15,
},
{
label: 'Tier1-A factory C',
date: '2022/11/7',
progress: 0.35,
},
{
label: 'Tier1-A factory C',
date: '2022/11/8',
progress: 0.45,
},
{
label: 'Tier1-A factory C',
date: '2022/11/9',
progress: 0.65,
},
{
label: 'Tier1-A factory C',
date: '2022/11/10',
progress: 0.75,
},
{
label: 'Tier1-A factory C',
date: '2022/11/11',
progress: 0.85,
},
{
label: 'Tier1-A factory C',
date: '2022/11/12',
progress: 0.95,
},
],
};
};
const getSampleData4 = () => {
return {
id: '部品納期管理/納期遵守率(ETD):月次',
category: '部品納期管理',
description:
'サプライヤの部品納期回答精度(月次)として、遅延日数の平均値、最大値、最小値、中央値、標準偏差を算出',
provide: true,
tags: ['SCM', '部品納期管理', '納期遵守率', '月次'],
lastUpdate: '2022/12/1',
version: 'v1.0.0',
in: [
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PRIMARYSUPPLIER_PRIMARYPART_SHIPMENT',
column: [
{
logicalName: 'サプライヤ名',
physicalName: 'SUPPLIER_NAME',
type: '文字列',
},
{
logicalName: '工場名',
physicalName: 'FACTORY_NAME',
type: '文字列',
},
],
},
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PRIMARYPART_ORDERPLAN_REPLY',
column: [
{
logicalName: '基準日',
physicalName: 'REF_DATE',
type: '日付',
},
],
},
],
out: [
{
db: 'INDUSTRIAL_DNA_DB',
schema: 'INDUSTRIAL_DNA_SCHEMA',
table: 'PARTSDELIVERY_ONTIMEDELIVERYRATE_MONTHLY_DELAYDAYS',
column: [
{
logicalName: 'サプライヤ名',
physicalName: 'SUPPLIER_NAME',
type: '文字列',
},
{
logicalName: '工場名',
physicalName: 'FACTORY_NAME',
type: '文字列',
},
{
logicalName: '基準日',
physicalName: 'REF_DATE',
type: '日付',
},
],
},
],
api: [
'PRIMARYSUPPLIER_PRIMARYPART_SHIPMENT',
'PRIMARYPART_ORDERPLAN_REPLY',
],
metrics_pattern: 'rate,qty',
graph_title: ['欠品率・欠品数'],
data: [
{
label: 'Tier1-A factory A',
date: '2022/11/6',
rate: 0,
qty: 0,
},
{
label: 'Tier1-A factory A',
date: '2022/11/7',
rate: 0.2,
qty: 33,
},
{
label: 'Tier1-A factory A',
date: '2022/11/8',
rate: 0.45,
qty: 10,
},
{
label: 'Tier1-A factory A',
date: '2022/11/9',
rate: 0.55,
qty: 10,
},
{
label: 'Tier1-A factory A',
date: '2022/11/10',
rate: 0.8,
qty: 10,
},
{
label: 'Tier1-A factory A',
date: '2022/11/11',
rate: 0.9,
qty: 10,
},
{
label: 'Tier1-A factory A',
date: '2022/11/12',
rate: 1,
qty: 10,
},
{
label: 'Tier1-A factory B',
date: '2022/11/6',
rate: 0.1,
qty: 10,
},
{
label: 'Tier1-A factory B',
date: '2022/11/7',
rate: 0.3,
qty: 10,
},
{
label: 'Tier1-A factory B',
date: '2022/11/8',
rate: 0.45,
qty: 11,
},
{
label: 'Tier1-A factory B',
date: '2022/11/9',
rate: 0.65,
qty: 9,
},
{
label: 'Tier1-A factory B',
date: '2022/11/10',
rate: 0.7,
qty: 24,
},
{
label: 'Tier1-A factory B',
date: '2022/11/11',
rate: 0.8,
qty: 6,
},
{
label: 'Tier1-A factory B',
date: '2022/11/12',
rate: 0.9,
qty: 10,
},
{
label: 'Tier1-A factory C',
date: '2022/11/6',
rate: 0.15,
qty: 10,
},
{
label: 'Tier1-A factory C',
date: '2022/11/7',
rate: 0.35,
qty: 5,
},
{
label: 'Tier1-A factory C',
date: '2022/11/8',
rate: 0.45,
},
{
label: 'Tier1-A factory C',
date: '2022/11/9',
rate: 0.65,
qty: 22,
},
{
label: 'Tier1-A factory C',
date: '2022/11/10',
rate: 0.75,
qty: 11,
},
{
label: 'Tier1-A factory C',
date: '2022/11/11',
rate: 0.85,
qty: 15,
},
{
label: 'Tier1-A factory C',
date: '2022/11/12',
rate: 0.95,
qty: 30,
},
],
};
};
import React from "react"
import { Spacer } from "../spacer";
import { useState, useEffect } from 'react';
import NewAPIDtlModal from './newAPIDtlModal'
import NewSampleDataModal from './newSampleDataModal'
import CreateNewApi from './createNewApi'
import EditApiList from './editApiList'
import { commonAjax } from '../../../components/commonAjax';
import * as Defs from './apiDefs';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({loading: true})
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = []
let tmpTags:string[]=[]
data.map((item:any)=>{
tmpTags=tmpTags.concat(item.tags)
})
tmpTags = Array.from(new Set(tmpTags))
tmpTags.map((t:string)=>{
resTags.push({tag_name:t,select:true})
})
setApiList(data);
setTags(resTags)
});
})();
}, [changeStatus]);
return (
<div className="content-wrapper scm-api-list">
<section className="page-cover">
<h1>SCM API一覧</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>SCM API一覧</h1>
<div className="content-header-desc">現在、設定されているAPI一覧です</div>
</div>
<div className="content-header-right">
<button className="btn btn-secondary" data-toggle="modal" data-target='#editApiListModal'>編集</button>
<button className="btn btn-primary" data-toggle="modal" data-target='#api-dtl-modal'>追加</button>
</div>
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({"size":30})}
{/* API List (Grid) */}
{viewList(apiList, tagList, changeStatus, setChangeStatus)}
</section>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</div>
);
}
// fillter tag
function FilterTags (tags:Defs.Tag[],setTags:any){
return(
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item:Defs.Tag) => {
if(item.select){
return(
<div className="btn btn-tag active"
onClick={()=>{
item.select = false;
let t = tags.slice(0,tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
)
}else{
return(
<div className="btn btn-tag"
onClick={()=>{
item.select = true;
let t = tags.slice(0,tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
)
}
})}
</div>
)
}
function viewList (item:Defs.Api[], tagList:Defs.Tag[], changeStatus:boolean, setChangeStatus:any){
// if(item.length){
const length = Math.ceil(item.length / 3)
let listItem:any=[]
if(length!=0){
listItem = transpose(new Array(length).fill(0).map((_, i) => item.slice(i * 3, (i + 1) * 3)))
}
return(
<div className="list-wrapper">
<div className="apiList">
{0 >= 0&&0<listItem.length?listItem[0].map((val: Defs.Api)=>ApiCard(val,tagList,changeStatus,setChangeStatus)):null}
</div>
<div className="apiList">
{1>= 0&&1<listItem.length?listItem[1].map((val: Defs.Api)=>ApiCard(val,tagList,changeStatus,setChangeStatus)):null}
</div>
<div className="apiList">
{2>= 0&&2<listItem.length?listItem[2].map((val: Defs.Api)=>ApiCard(val,tagList,changeStatus,setChangeStatus)):null}
</div>
</div>
)
// }
}
// API Card
function ApiCard(api: Defs.Api,tagList:Defs.Tag[],changeStatus:boolean,setChangeStatus:any) {
let selected = false
tagList.map((item:Defs.Tag)=>{
api.tags.map((apiTag:string)=>{
if(apiTag==item.tag_name){
if(item.select){
selected=true
}
}
})
})
$('#'+api.physical_name).collapse("hide");
return (
<>
<div className={`card ${selected?'card-primary':'card-not-applicable not-applicable'} card-collapse-sample`}>
<div className="card-header collapsed" data-toggle="collapse" data-target={'#'+api.physical_name}>
<div className="api-name-font" style={{fontSize: `clamp(0.6rem, ${29 / (api.physical_name+' / '+api.logical_name).length}vw, 1rem)`}}>
<i
className={`fa fa-circle provide-icon-size ${
selected?api.provide? 'provide-icon-color': 'no-provide-icon-color': undefined
}`}
aria-hidden="true"
/>
{Spacer({"size":10, horizontal:true})}
{api.physical_name+' / '+api.logical_name}
</div>
</div>
<div className="card-body collapse" id={api.physical_name}>
<div className="api-list-title">
<p>
API 連携項目 一覧
</p>
{Spacer({"size":20})}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = []
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid">
<p>{item.logicalName}</p>
</div>
)
})
return res
})}
{Spacer({"size":20})}
<div className="btn-center">
{api.provide
?
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
// data-target={"#"+api.physical_name+"-dtl-modal"}
data-target="test"
>
サンプルデータ挿入 / API連携項目追加
</button>
:
<button
className="btn btn-secondary btn-secondary"
onClick={()=>{putDeploy(api,changeStatus,setChangeStatus)}}
>
Deploy
</button>
}
</div>
{/* <div>{NewAPIDtlModal(api)}</div> */}
<NewAPIDtlModal val={api} />
<NewSampleDataModal id={api.physical_name}/>
</div>
</div>
</>
)
}
// Sample Modal
function Modal() {
return (
<>
{/* Content Header (Page header) */}
<div className="modal fade" id="sampleModal1">
<div className="modal-dialog modal-xl">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">SAMPLE MODAL</h4>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<p>BODY</p>
</div>
</div>
{/* <!-- /.modal-content --> */}
</div>
{/* <!-- /.modal-dialog --> */}
</div>
{/* <!-- /.modal --> */}
{/* /.content */}
</>
)
}
// transpose columns and rows
const transpose = (a: any[]) => a[0].map((_: any, c: string | number) => a.map(r => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(api:Defs.Api,changeStatus:boolean,setChangeStatus:any){
if (api) {
if (!api.provide) {
(async () => {
commonAjax
.axios({swalFire: true, loading: true})
.put(`/api/api/${api.physical_name}`)
.then((res) => {
setChangeStatus(changeStatus?false:true);
});
})();
}
}
}
export default NewApiList;
import React from "react"
import { Spacer } from "../spacer";
import { useState, useEffect } from 'react';
import NewAPIDtlModal from './newAPIDtlModal'
import NewSampleDataModal from './newSampleDataModal'
import CreateNewApi from './createNewApi'
import EditApiList from './editApiList'
import { commonAjax } from '../../../components/commonAjax';
import * as Defs from './apiDefs';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({loading: true})
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = []
let tmpTags:string[]=[]
data.map((item:any)=>{
tmpTags=tmpTags.concat(item.tags)
})
tmpTags = Array.from(new Set(tmpTags))
tmpTags.map((t:string)=>{
resTags.push({tag_name:t,select:true})
})
setApiList(data);
setTags(resTags)
});
})();
}, [changeStatus]);
return (
<div className="content-wrapper scm-api-list">
<section className="page-cover">
<h1>SCM API一覧</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>SCM API一覧</h1>
<div className="content-header-desc">現在、設定されているAPI一覧です</div>
</div>
<div className="content-header-right">
<button className="btn btn-secondary" data-toggle="modal" data-target='#editApiListModal'>編集</button>
<button className="btn btn-primary" data-toggle="modal" data-target='#api-dtl-modal'>追加</button>
</div>
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({"size":30})}
{/* API List (Grid) */}
{viewList(apiList, tagList, changeStatus, setChangeStatus)}
</section>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</div>
);
}
// fillter tag
function FilterTags (tags:Defs.Tag[],setTags:any){
return(
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item:Defs.Tag) => {
if(item.select){
return(
<div className="btn btn-tag active"
onClick={()=>{
item.select = false;
let t = tags.slice(0,tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
)
}else{
return(
<div className="btn btn-tag"
onClick={()=>{
item.select = true;
let t = tags.slice(0,tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
)
}
})}
</div>
)
}
function viewList (item:Defs.Api[], tagList:Defs.Tag[], changeStatus:boolean, setChangeStatus:any){
// if(item.length){
const length = Math.ceil(item.length / 3)
let listItem:any=[]
if(length!=0){
listItem = transpose(new Array(length).fill(0).map((_, i) => item.slice(i * 3, (i + 1) * 3)))
}
return(
<div className="list-wrapper">
<div className="apiList">
{0 >= 0&&0<listItem.length?listItem[0].map((val: Defs.Api)=>ApiCard(val,tagList,changeStatus,setChangeStatus)):null}
</div>
<div className="apiList">
{1>= 0&&1<listItem.length?listItem[1].map((val: Defs.Api)=>ApiCard(val,tagList,changeStatus,setChangeStatus)):null}
</div>
<div className="apiList">
{2>= 0&&2<listItem.length?listItem[2].map((val: Defs.Api)=>ApiCard(val,tagList,changeStatus,setChangeStatus)):null}
</div>
</div>
)
// }
}
// API Card
function ApiCard(api: Defs.Api,tagList:Defs.Tag[],changeStatus:boolean,setChangeStatus:any) {
let selected = false
tagList.map((item:Defs.Tag)=>{
api.tags.map((apiTag:string)=>{
if(apiTag==item.tag_name){
if(item.select){
selected=true
}
}
})
})
$('#'+api.physical_name).collapse("hide");
return (
<>
<div className={`card ${selected?'card-primary':'card-not-applicable not-applicable'} card-collapse-sample`}>
<div className="card-header collapsed" data-toggle="collapse" data-target={'#'+api.physical_name}>
<div className="api-name-font" style={{fontSize: `clamp(0.6rem, ${29 / (api.physical_name+' / '+api.logical_name).length}vw, 1rem)`}}>
<i
className={`fa fa-circle provide-icon-size ${
selected?api.provide? 'provide-icon-color': 'no-provide-icon-color': undefined
}`}
aria-hidden="true"
/>
{Spacer({"size":10, horizontal:true})}
{api.physical_name+' / '+api.logical_name}
</div>
</div>
<div className="card-body collapse" id={api.physical_name}>
<div className="api-list-title">
<p>
API 連携項目 一覧
</p>
{Spacer({"size":20})}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = []
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid">
<p>{item.logicalName}</p>
</div>
)
})
return res
})}
{Spacer({"size":20})}
<div className="btn-center">
{api.provide
?
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={"#"+api.physical_name+"-dtl-modal"}
>
サンプルデータ挿入 / API連携項目追加
</button>
:
<button
className="btn btn-secondary btn-secondary"
onClick={()=>{putDeploy(api,changeStatus,setChangeStatus)}}
>
Deploy
</button>
}
</div>
{/* <div>{NewAPIDtlModal(api)}</div> */}
<NewAPIDtlModal val={api} />
<NewSampleDataModal id={api.physical_name}/>
</div>
</div>
</>
)
}
// Sample Modal
function Modal() {
return (
<>
{/* Content Header (Page header) */}
<div className="modal fade" id="sampleModal1">
<div className="modal-dialog modal-xl">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">SAMPLE MODAL</h4>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<p>BODY</p>
</div>
</div>
{/* <!-- /.modal-content --> */}
</div>
{/* <!-- /.modal-dialog --> */}
</div>
{/* <!-- /.modal --> */}
{/* /.content */}
</>
)
}
// transpose columns and rows
const transpose = (a: any[]) => a[0].map((_: any, c: string | number) => a.map(r => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(api:Defs.Api,changeStatus:boolean,setChangeStatus:any){
if (api) {
if (!api.provide) {
(async () => {
commonAjax
.axios({swalFire: true, loading: true})
.put(`/api/api/${api.physical_name}`)
.then((res) => {
setChangeStatus(changeStatus?false:true);
});
})();
}
}
}
export default NewApiList;
import React, { ChangeEvent, useState } from "react";
import { useForm } from 'react-hook-form';
import { Spacer } from "../spacer";
import { commonAjax } from '../../../components/commonAjax';
import * as Defs from './apiDefs';
const NewAPIDtlModal = (props: { val: Defs.Api }) => {
// プルダウンの選択肢
const gender = [
{ key: 'integer', label: 'integer' },
{ key: 'long', label: 'long' },
{ key: 'float', label: 'float' },
{ key: 'double', label: 'double' },
{ key: 'string', label: 'string' },
{ key: 'byte', label: 'byte' },
{ key: 'binary', label: 'binary' },
{ key: 'boolean', label: 'boolean(true,false)' },
{ key: 'date', label: 'date(yyyy-mm-dd)' },
{ key: 'dateTime', label: 'dateTime(yyyy-mm-ddTHH:MM:SSZ)' },
];
// api情報をコピー
const tmp = JSON.parse(JSON.stringify(props.val))
// apiの各項目に削除、編集中、編集済のフラグを付与
tmp.outinfo[0].column.map((col:Defs.EditColumn)=>{
col["delete"] = false;
col["editing"] = false;
col["edited"] = false;
})
// useState
const origin = JSON.parse(JSON.stringify(tmp))
const [editAPI, setEditAPI] = useState<Defs.EditApi>(tmp);
const [editIndex, setEditIndex] = useState<string>("")
const [reflectLock, setReflectLock] = useState<boolean>(true)
const [updated, setUpdated] = useState<boolean>(false)
// input form
const [colPhysicalName, setColPhysicalName] = useState<string>("")
const [colLogicalName, setColLogicalName] = useState<string>("")
const [colType, setColType] = useState<string>("")
// react hook form
const {
register,
resetField,
formState: { isDirty, isValid, errors },
} = useForm({
defaultValues: { colLogicalName: '', colPhysicalName:'', colType:''},
mode: 'onChange',
criteriaMode: 'all',
});
const colLogicalNameField = register('colLogicalName', {
required: {
value: true,
message: '論理名は入力が必須の項目です',
},
});
const colPhysicalNameField = register('colPhysicalName', {
required: {
value: true,
message: '物理名は入力が必須の項目です',
},
pattern: {
value: /^[0-9a-zA-Z_]+$/,
message: '物理名には英数字およびアンダースコアのみ使用可能です',
}
});
const colTypeField = register('colType', {
required: {
value: true,
message: 'データ型は入力が必須の項目です',
},
});
// 物理名、論理名が入力された際に値を設定する
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
switch (e.target.name) {
case 'colLogicalName':
setColLogicalName(e.target.value);
break;
case 'colPhysicalName':
setColPhysicalName(e.target.value);
break;
}
};
// データ型が入力された際に値を設定する
const selectChange = (e: ChangeEvent<HTMLSelectElement>) => {
setColType(e.target.value);
};
// 更新と追加のボタン切り替え
const addOrUpd = ()=>{
if(editIndex===""){
return(
<div className="btn-right">
<button className="btn btn-secondary"
disabled={!isDirty || !isValid}
onClick={()=>{
const addCol={
"physicalName":colPhysicalName,
"logicalName":colLogicalName,
"type":colType,
"required":$('#'+editAPI.physical_name+'-required').prop('checked'),
"delete":false,
"editing":false,
"edited":true
}
editAPI.outinfo[0].column.push(addCol)
setEditAPI({...editAPI})
formReset()
if(reflectLock){
$('#'+editAPI.physical_name+'-error').text("")
}
}}
>
追加する
</button>
</div>
)
} else {
return(
<div className="btn-right">
<button className="btn btn-secondary"
disabled={
Boolean(errors.colLogicalName)
|| Boolean(errors.colPhysicalName)
|| Boolean(errors.colType)
}
onClick={()=>{
const addCol={
"physicalName":colPhysicalName,
"logicalName":colLogicalName,
"type":colType,
"required":$('#'+editAPI.physical_name+'-required').prop('checked'),
"delete":false,
"editing":false,
"edited":true
}
editAPI.outinfo[0].column[Number(editIndex)] = addCol
setEditAPI({...editAPI})
setEditIndex("")
formReset()
}}
>
更新する
</button>
</div>
)
}
}
// 削除と復元のボタン切り替え
const deleteOrUndo = (item:Defs.EditColumn) => {
if(!item.delete) {
return(
<i className="fa fa-times-circle" aria-hidden="true"
onClick={()=>{
editCancel(item)
item.delete=true
setEditAPI({...editAPI})
}}
></i>
)
} else {
return(
<i className="fa fa-reply" aria-hidden="true"
onClick={()=>{
item.delete=false
formReset()
if(reflectLock){
$('#'+editAPI.physical_name+'-error').text("")
}
setEditAPI({...editAPI})
}}
></i>
)
}
}
// 編集中断処理
const editCancel = (item:Defs.EditColumn) => {
formReset()
setEditIndex("")
item.editing=false
}
// フォームの初期化
const formReset = () => {
$('#'+editAPI.physical_name+'-required').prop('checked',false);
setColLogicalName("")
setColPhysicalName("")
setColType("")
resetField('colLogicalName')
resetField('colPhysicalName')
resetField('colType')
}
// put api(add col or delete col)
function putChangeAPI(){
(async () => {
let apiList = JSON.parse(JSON.stringify(editAPI))
apiList.outinfo[0].column.map((item:any, index:number)=>{
if(item.delete){
apiList.outinfo[0].column[index] = {}
} else {
delete item["delete"]
delete item["editing"]
delete item["edited"]
}
})
apiList.outinfo[0].column = apiList.outinfo[0].column.filter((tmp:any) => Object.keys(tmp).length)
commonAjax
.axios({swalFire: true, loading: true})
.put(`/api/api/${editAPI.physical_name}`, apiList.outinfo[0].column)
.then((res) => {
apiList.outinfo[0].column.map((col:Defs.EditColumn)=>{
col["delete"] = false;
col["editing"] = false;
col["edited"] = false;
})
setEditAPI(apiList)
setUpdated(true)
});
})();
}
// モーダルが閉じられた場合、表示するカラムをもとに戻す
$("#"+props.val.physical_name+"-dtl-modal").on("hidden.bs.modal",()=>{
if(!updated){
setEditAPI(JSON.parse(JSON.stringify(origin)))
setEditIndex("")
setReflectLock(true)
formReset()
$('#'+editAPI.physical_name+'-error').text("")
$("#"+props.val.physical_name+"-dtl-modal").off("hidden.bs.modal")
}else{
setUpdated(false)
window.location.reload()
}
})
return (
<>
{/* Add API col Modal */}
{/* Content Header (Page header) */}
<div className="modal fade" id={editAPI.physical_name+"-dtl-modal"}>
<div className="modal-dialog api-dtl-modal-width">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">サンプルデータ挿入 / API連携項目追加</h4>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
{/* API name display area */}
<div className="name-disp-area">
<div className="api-name">
<div className="dtl-content-width">API 論理名</div>
<div className="dtl-modal-font-color">{editAPI.logical_name}</div>
</div>
<div className="api-name">
<div className="dtl-content-width">API 物理名</div>
<div className="dtl-modal-font-color">{editAPI.physical_name}</div>
</div>
<div className="btn-right">
<button className="btn btn-secondary"
onClick={()=>{
$("#"+editAPI.physical_name+"-dtl-modal").modal("hide").on("hidden.bs.modal",()=>{
setTimeout(()=>$('#'+editAPI.physical_name.replaceAll('_', '')+"sample-data").modal("show"))
$("#"+editAPI.physical_name+"-dtl-modal").off("hidden.bs.modal")
})
}}>
サンプルデータ挿入
</button>
</div>
</div>
{Spacer({"size":10})}
<div className="dividing-line"/>
{/* API col display area */}
{Spacer({"size":10})}
<div className="col-disp-title">すでに連携されている連携項目</div>
{Spacer({"size":20})}
{editAPI.outinfo.map((val: Defs.EditTableInfo) => {
const res: any[] = []
val.column.map((item: Defs.EditColumn, index:number) => {
res.push(
<div className={`add-col-grid col-disp-grid ${item.delete?"delete-col":item.editing?"editing-col":item.edited&&"edited-col"}`}>
<div className="add-item">
<div className="add-input-form-title"/>
<p className="adjust-indent-base adjust-indent-check">{item.required&&"✔"}</p>
</div>
<div className="add-item">
<div className="add-input-form-title"/>
<p className="adjust-indent-base adjust-indent-form">{item.logicalName}</p>
</div>
<div className="add-item">
<div className="add-input-form-title"/>
<p className="adjust-indent-base adjust-indent-form">{item.physicalName}</p>
</div>
<div className="add-item">
<div className="add-input-form-title"/>
<p className="adjust-indent-base adjust-indent-type">{item.type}</p>
</div>
<div className="btn-left">
<i className={`fa fa-solid fa-pen ${item.delete&&"not-applicable"}`}
aria-hidden="true"
onClick={()=>{
if(item.editing){
editCancel(item)
}else{
if(editIndex!==""){
editAPI.outinfo[0].column[Number(editIndex)].editing=false
}
formReset()
setColLogicalName(item.logicalName)
setColPhysicalName(item.physicalName)
setColType(item.type)
$('#'+editAPI.physical_name+'-required').prop('checked',item.required);
item.editing=true
setEditIndex(String(index))
}
}}
></i>
{Spacer({"size":50,horizontal:true})}
{deleteOrUndo(item)}
</div>
</div>
)
})
return res
})}
{Spacer({"size":30})}
<div className="dividing-line"/>
{/* Add col input area */}
{Spacer({"size":30})}
<div className="add-col-grid">
<div className="add-item">
<div className="add-input-form-title">必須</div>
<input type="checkbox" id={editAPI.physical_name+"-required"} className="form-check-input" name={editAPI.physical_name+"-required"} value="1"/>
<label className="form-check-label check-only input-data-check" htmlFor={editAPI.physical_name+"-required"}></label>
</div>
<div className="add-item">
<div className="add-input-form-title">論理名</div>
<input
type="text"
id={editAPI.physical_name+"-add-col-l-name"}
className="form-control add-input-form"
value={colLogicalName}
{...colLogicalNameField}
onChange={(e) => {
colLogicalNameField.onChange(e);
handleChange(e);
}}
/>
</div>
<div className="add-item">
<div className="add-input-form-title">物理名</div>
<input
type="text"
id={editAPI.physical_name+"-add-col-p-name"}
className="form-control add-input-form"
value={colPhysicalName}
{...colPhysicalNameField}
onChange={(e) => {
colPhysicalNameField.onChange(e);
handleChange(e);
}}
/>
</div>
<div className="add-item">
<div className="add-input-form-title">データ型</div>
<select
id={editAPI.physical_name+"-category-sel"}
className="form-control input-data-type"
required
value={colType}
{...colTypeField}
onChange={(e) => {
colTypeField.onChange(e);
selectChange(e);
}}
>
<option hidden value="">選択する</option>
{gender.map((item, i) => (
<option
value={item.key}
key={item.key}
>
{item.label}
</option>
))}
</select>
</div>
{addOrUpd()}
</div>
{Spacer({"size":25})}
<div className="error-msg-area">
{errors.colLogicalName?.message && (
<div className="text-danger">{errors.colLogicalName.message}</div>
)}
{errors.colPhysicalName?.message && (
<div className="text-danger">{errors.colPhysicalName.message}</div>
)}
{errors.colType?.message && (
<div className="text-danger">{errors.colType.message}</div>
)}
<div className="text-danger" id={editAPI.physical_name+'-error'}></div>
</div>
<div className="reflect-col-btn">
<button className="btn btn-primary"
onClick={()=>{
let flg = true
editAPI.outinfo[0].column.map((item:Defs.EditColumn)=>{
if(!item.delete){
flg = false
setReflectLock(false)
}
})
if(!flg){
putChangeAPI()
}else{
$('#'+editAPI.physical_name+'-error').text("連携項目は1つ以上必要です")
}
}}>
反映する
</button>
</div>
{Spacer({"size":30})}
</div>
</div>
{/* <!-- /.modal-content --> */}
</div>
{/* <!-- /.modal-dialog --> */}
</div>
{/* <!-- /.modal --> */}
</>
)
}
export default NewAPIDtlModal;
~~~
import React from "react"
import { Spacer } from "../spacer";
import { useState, useEffect } from 'react';
import NewAPIDtlModal from './newAPIDtlModal'
import NewSampleDataModal from './newSampleDataModal'
import CreateNewApi from './createNewApi'
import EditApiList from './editApiList'
import { commonAjax } from '../../../components/commonAjax';
import * as Defs from './apiDefs';
// main
function NewApiList() {
// useState
const [apiList, setApiList] = useState<Defs.Api[]>([]);
const [tagList, setTags] = useState<Defs.Tag[]>([]);
const [changeStatus, setChangeStatus] = useState<boolean>(false);
/* API情報取得のリクエスト */
useEffect(() => {
(async () => {
commonAjax
.axios({loading: true})
.get('/api/api')
.then((res) => {
const data = res.data.api;
const resTags: React.SetStateAction<Defs.Tag[]> = []
let tmpTags:string[]=[]
data.map((item:any)=>{
tmpTags=tmpTags.concat(item.tags)
})
tmpTags = Array.from(new Set(tmpTags))
tmpTags.map((t:string)=>{
resTags.push({tag_name:t,select:true})
})
setApiList(data);
setTags(resTags)
});
})();
}, [changeStatus]);
return (
<div className="content-wrapper scm-api-list">
<section className="page-cover">
<h1>SCM API一覧</h1>
</section>
{/* Content Header (Page header) */}
<section className="content-header">
<div className="content-header-left">
<h1>SCM API一覧</h1>
<div className="content-header-desc">現在、設定されているAPI一覧です</div>
</div>
<div className="content-header-right">
<button className="btn btn-secondary" data-toggle="modal" data-target='#editApiListModal'>編集</button>
<button className="btn btn-primary" data-toggle="modal" data-target='#api-dtl-modal'>追加</button>
</div>
</section>
<section className="content">
{/* filter tag */}
<div className="inline-form">
<div className="inline-form-cat">フィルタータグ</div>
{FilterTags(tagList, setTags)}
</div>
{Spacer({"size":30})}
{/* API List (Grid) */}
{viewList(apiList, tagList, changeStatus, setChangeStatus)}
</section>
<CreateNewApi
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
<EditApiList
val={apiList}
changeStatus={changeStatus}
setChangeStatus={setChangeStatus}
/>
</div>
);
}
// fillter tag
function FilterTags (tags:Defs.Tag[],setTags:any){
return(
<div className="inline-form-group">
<div className="inline-form-label">分類</div>
{tags.map((item:Defs.Tag) => {
if(item.select){
return(
<div className="btn btn-tag active"
onClick={()=>{
item.select = false;
let t = tags.slice(0,tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
)
}else{
return(
<div className="btn btn-tag"
onClick={()=>{
item.select = true;
let t = tags.slice(0,tags.length);
setTags(t);
}}
>
{item.tag_name}
</div>
)
}
})}
</div>
)
}
function viewList (item:Defs.Api[], tagList:Defs.Tag[], changeStatus:boolean, setChangeStatus:any){
// if(item.length){
const length = Math.ceil(item.length / 3)
let listItem:any=[]
if(length!=0){
listItem = transpose(new Array(length).fill(0).map((_, i) => item.slice(i * 3, (i + 1) * 3)))
}
return(
<div className="list-wrapper">
<div className="apiList">
{0 >= 0&&0<listItem.length?listItem[0].map((val: Defs.Api)=>ApiCard(val,tagList,changeStatus,setChangeStatus)):null}
</div>
<div className="apiList">
{1>= 0&&1<listItem.length?listItem[1].map((val: Defs.Api)=>ApiCard(val,tagList,changeStatus,setChangeStatus)):null}
</div>
<div className="apiList">
{2>= 0&&2<listItem.length?listItem[2].map((val: Defs.Api)=>ApiCard(val,tagList,changeStatus,setChangeStatus)):null}
</div>
</div>
)
// }
}
// API Card
function ApiCard(api: Defs.Api,tagList:Defs.Tag[],changeStatus:boolean,setChangeStatus:any) {
let selected = false
tagList.map((item:Defs.Tag)=>{
api.tags.map((apiTag:string)=>{
if(apiTag==item.tag_name){
if(item.select){
selected=true
}
}
})
})
$('#'+api.physical_name).collapse("hide");
return (
<>
<div className={`card ${selected?'card-primary':'card-not-applicable not-applicable'} card-collapse-sample`}>
<div className="card-header collapsed" data-toggle="collapse" data-target={'#'+api.physical_name}>
<div className="api-name-font" style={{fontSize: `clamp(0.6rem, ${29 / (api.physical_name+' / '+api.logical_name).length}vw, 1rem)`}}>
<i
className={`fa fa-circle provide-icon-size ${
selected?api.provide? 'provide-icon-color': 'no-provide-icon-color': undefined
}`}
aria-hidden="true"
/>
{Spacer({"size":10, horizontal:true})}
{api.physical_name+' / '+api.logical_name}
</div>
</div>
<div className="card-body collapse" id={api.physical_name}>
<div className="api-list-title">
<p>
API 連携項目 一覧
</p>
{Spacer({"size":20})}
</div>
{api.outinfo.map((val: Defs.TableInfo) => {
const res: JSX.Element[] = []
val.column.map((item: Defs.Column) => {
res.push(
<div className="list-contents-grid">
<p>{item.logicalName}</p>
</div>
)
})
return res
})}
{Spacer({"size":20})}
<div className="btn-center">
{api.provide
?
<button
className="btn btn-secondary btn-secondary"
data-toggle="modal"
data-target={"#"+api.physical_name+"-dtl-modal"}
>
サンプルデータ挿入 / API連携項目追加
</button>
:
<button
className="btn btn-secondary btn-secondary"
onClick={()=>{putDeploy(api,changeStatus,setChangeStatus)}}
>
Deploy
</button>
}
</div>
{/* <div>{NewAPIDtlModal(api)}</div> */}
<NewAPIDtlModal val={api} />
<NewSampleDataModal id={api.physical_name}/>
</div>
</div>
</>
)
}
// Sample Modal
function Modal() {
return (
<>
{/* Content Header (Page header) */}
<div className="modal fade" id="sampleModal1">
<div className="modal-dialog modal-xl">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">SAMPLE MODAL</h4>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<p>BODY</p>
</div>
</div>
{/* <!-- /.modal-content --> */}
</div>
{/* <!-- /.modal-dialog --> */}
</div>
{/* <!-- /.modal --> */}
{/* /.content */}
</>
)
}
// transpose columns and rows
const transpose = (a: any[]) => a[0].map((_: any, c: string | number) => a.map(r => r[c]).filter(Boolean));
// put api(deploy)
function putDeploy(api:Defs.Api,changeStatus:boolean,setChangeStatus:any){
if (api) {
if (!api.provide) {
(async () => {
commonAjax
.axios({swalFire: true, loading: true})
.put(`/api/api/${api.physical_name}`)
.then((res) => {
setChangeStatus(changeStatus?false:true);
});
})();
}
}
}
export default NewApiList;
import React from "react";
function TestModal() { return (
test
); }
export default TestModal;
`import React from "react";
function TestModal() { return (
test
); }
export default TestModal; `
import React from "react";
function TestModal() {
return (
<div id="testModal" className="modal">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">テストモーダル</h5>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<p>test</p>
</div>
</div>
</div>
</div>
);
}
export default TestModal;
import React from "react";
import { useState } from 'react';
function TestModal() {
const [showModal, setShowModal] = useState(false);
const openModal = () => {
setShowModal(true);
};
const closeModal = () => {
setShowModal(false);
};
return (
<div className={`modal ${showModal ? 'show' : ''}`} id="testModal" tabIndex="-1" role="dialog">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">テストモーダル</h5>
<button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={closeModal}>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<p>test</p>
</div>
</div>
</div>
</div>
);
}
// main
function NewApiList() {
// ...
return (
<div className="content-wrapper scm-api-list">
{/* ... */}
<section className="content">
{/* ... */}
<button className="btn btn-primary" onClick={openModal}>テストモーダルを開く</button>
{/* ... */}
</section>
<TestModal />
</div>
);
}
export default NewApiList;
import React, { useState } from "react";
function TestModal({ showModal, closeModal }) {
return (
<div className={`modal ${showModal ? 'show' : ''}`} id="testModal" tabIndex="-1" role="dialog">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">テストモーダル</h5>
<button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={closeModal}>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<p>test</p>
</div>
</div>
</div>
</div>
);
}
// main
function NewApiList() {
const [showModal, setShowModal] = useState(false);
const openModal = () => {
setShowModal(true);
};
const closeModal = () => {
setShowModal(false);
};
return (
<div className="content-wrapper scm-api-list">
{/* ... */}
<section className="content">
{/* ... */}
<button className="btn btn-primary" onClick={openModal}>テストモーダルを開く</button>
{/* ... */}
</section>
<TestModal showModal={showModal} closeModal={closeModal} />
</div>
);
}
export default NewApiList;
import React, { useState } from "react";
function TestModal({ showModal, closeModal }) {
return (
<div className={`modal ${showModal ? 'show' : ''}`} id="testModal" tabIndex="-1" role="dialog">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">テストモーダル</h5>
<button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={closeModal}>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<p>test</p>
</div>
</div>
</div>
</div>
);
}
// main
function NewApiList() {
const [showModal, setShowModal] = useState(false);
const openModal = () => {
setShowModal(true);
};
const closeModal = () => {
setShowModal(false);
};
return (
<div className="content-wrapper scm-api-list">
{/* ... */}
<section className="content">
{/* ... */}
<button className="btn btn-primary" onClick={openModal}>テストモーダルを開く</button>
{/* ... */}
</section>
<TestModal showModal={showModal} closeModal={closeModal} />
</div>
);
}
export default NewApiList;
import React, { ChangeEvent, useState } from "react";
import { useForm } from 'react-hook-form';
import { Spacer } from "../spacer";
import { commonAjax } from '../../../components/commonAjax';
import * as Defs from './apiDefs';
function TestModal() {
return (
<div id="testModal" className="modal">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">テストモーダル</h5>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<p>test</p>
</div>
</div>
</div>
</div>
);
}
const NewAPIDtlModal = (props: { val: Defs.Api }) => {
// プルダウンの選択肢
const gender = [
{ key: 'integer', label: 'integer' },
{ key: 'long', label: 'long' },
{ key: 'float', label: 'float' },
{ key: 'double', label: 'double' },
{ key: 'string', label: 'string' },
{ key: 'byte', label: 'byte' },
{ key: 'binary', label: 'binary' },
{ key: 'boolean', label: 'boolean(true,false)' },
{ key: 'date', label: 'date(yyyy-mm-dd)' },
{ key: 'dateTime', label: 'dateTime(yyyy-mm-ddTHH:MM:SSZ)' },
];
// api情報をコピー
const tmp = JSON.parse(JSON.stringify(props.val))
// apiの各項目に削除、編集中、編集済のフラグを付与
tmp.outinfo[0].column.map((col:Defs.EditColumn)=>{
col["delete"] = false;
col["editing"] = false;
col["edited"] = false;
})
// useState
const origin = JSON.parse(JSON.stringify(tmp))
const [editAPI, setEditAPI] = useState<Defs.EditApi>(tmp);
const [editIndex, setEditIndex] = useState<string>("")
const [reflectLock, setReflectLock] = useState<boolean>(true)
const [updated, setUpdated] = useState<boolean>(false)
// input form
const [colPhysicalName, setColPhysicalName] = useState<string>("")
const [colLogicalName, setColLogicalName] = useState<string>("")
const [colType, setColType] = useState<string>("")
// react hook form
const {
register,
resetField,
formState: { isDirty, isValid, errors },
} = useForm({
defaultValues: { colLogicalName: '', colPhysicalName:'', colType:''},
mode: 'onChange',
criteriaMode: 'all',
});
const colLogicalNameField = register('colLogicalName', {
required: {
value: true,
message: '論理名は入力が必須の項目です',
},
});
const colPhysicalNameField = register('colPhysicalName', {
required: {
value: true,
message: '物理名は入力が必須の項目です',
},
pattern: {
value: /^[0-9a-zA-Z_]+$/,
message: '物理名には英数字およびアンダースコアのみ使用可能です',
}
});
const colTypeField = register('colType', {
required: {
value: true,
message: 'データ型は入力が必須の項目です',
},
});
// 物理名、論理名が入力された際に値を設定する
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
switch (e.target.name) {
case 'colLogicalName':
setColLogicalName(e.target.value);
break;
case 'colPhysicalName':
setColPhysicalName(e.target.value);
break;
}
};
// データ型が入力された際に値を設定する
const selectChange = (e: ChangeEvent<HTMLSelectElement>) => {
setColType(e.target.value);
};
// 更新と追加のボタン切り替え
const addOrUpd = ()=>{
if(editIndex===""){
return(
<div className="btn-right">
<button className="btn btn-secondary"
disabled={!isDirty || !isValid}
onClick={()=>{
const addCol={
"physicalName":colPhysicalName,
"logicalName":colLogicalName,
"type":colType,
"required":$('#'+editAPI.physical_name+'-required').prop('checked'),
"delete":false,
"editing":false,
"edited":true
}
editAPI.outinfo[0].column.push(addCol)
setEditAPI({...editAPI})
formReset()
if(reflectLock){
$('#'+editAPI.physical_name+'-error').text("")
}
}}
>
追加する
</button>
</div>
)
} else {
return(
<div className="btn-right">
<button className="btn btn-secondary"
disabled={
Boolean(errors.colLogicalName)
|| Boolean(errors.colPhysicalName)
|| Boolean(errors.colType)
}
onClick={()=>{
const addCol={
"physicalName":colPhysicalName,
"logicalName":colLogicalName,
"type":colType,
"required":$('#'+editAPI.physical_name+'-required').prop('checked'),
"delete":false,
"editing":false,
"edited":true
}
editAPI.outinfo[0].column[Number(editIndex)] = addCol
setEditAPI({...editAPI})
setEditIndex("")
formReset()
}}
>
更新する
</button>
</div>
)
}
}
// 削除と復元のボタン切り替え
const deleteOrUndo = (item:Defs.EditColumn) => {
if(!item.delete) {
return(
<i className="fa fa-times-circle" aria-hidden="true"
onClick={()=>{
editCancel(item)
item.delete=true
setEditAPI({...editAPI})
}}
></i>
)
} else {
return(
<i className="fa fa-reply" aria-hidden="true"
onClick={()=>{
item.delete=false
formReset()
if(reflectLock){
$('#'+editAPI.physical_name+'-error').text("")
}
setEditAPI({...editAPI})
}}
></i>
)
}
}
// 編集中断処理
const editCancel = (item:Defs.EditColumn) => {
formReset()
setEditIndex("")
item.editing=false
}
// フォームの初期化
const formReset = () => {
$('#'+editAPI.physical_name+'-required').prop('checked',false);
setColLogicalName("")
setColPhysicalName("")
setColType("")
resetField('colLogicalName')
resetField('colPhysicalName')
resetField('colType')
}
// put api(add col or delete col)
function putChangeAPI(){
(async () => {
let apiList = JSON.parse(JSON.stringify(editAPI))
apiList.outinfo[0].column.map((item:any, index:number)=>{
if(item.delete){
apiList.outinfo[0].column[index] = {}
} else {
delete item["delete"]
delete item["editing"]
delete item["edited"]
}
})
apiList.outinfo[0].column = apiList.outinfo[0].column.filter((tmp:any) => Object.keys(tmp).length)
commonAjax
.axios({swalFire: true, loading: true})
.put(`/api/api/${editAPI.physical_name}`, apiList.outinfo[0].column)
.then((res) => {
apiList.outinfo[0].column.map((col:Defs.EditColumn)=>{
col["delete"] = false;
col["editing"] = false;
col["edited"] = false;
})
setEditAPI(apiList)
setUpdated(true)
});
})();
}
// モーダルが閉じられた場合、表示するカラムをもとに戻す
$("#"+props.val.physical_name+"-dtl-modal").on("hidden.bs.modal",()=>{
if(!updated){
setEditAPI(JSON.parse(JSON.stringify(origin)))
setEditIndex("")
setReflectLock(true)
formReset()
$('#'+editAPI.physical_name+'-error').text("")
$("#"+props.val.physical_name+"-dtl-modal").off("hidden.bs.modal")
}else{
setUpdated(false)
window.location.reload()
}
})
return (
<>
{/* Add API col Modal */}
{/* Content Header (Page header) */}
<div className="modal fade" id={editAPI.physical_name+"-dtl-modal"}>
<div className="modal-dialog api-dtl-modal-width">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">サンプルデータ挿入 / API連携項目追加</h4>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
{/* API name display area */}
<div className="name-disp-area">
<div className="api-name">
<div className="dtl-content-width">API 論理名</div>
<div className="dtl-modal-font-color">{editAPI.logical_name}</div>
</div>
<div className="api-name">
<div className="dtl-content-width">API 物理名</div>
<div className="dtl-modal-font-color">{editAPI.physical_name}</div>
</div>
<div className="btn-right">
<button className="btn btn-secondary"
onClick={()=>{
$("#"+editAPI.physical_name+"-dtl-modal").modal("hide").on("hidden.bs.modal",()=>{
setTimeout(()=>$('#'+editAPI.physical_name.replaceAll('_', '')+"sample-data").modal("show"))
$("#"+editAPI.physical_name+"-dtl-modal").off("hidden.bs.modal")
})
}}>
サンプルデータ挿入
</button>
</div>
</div>
{Spacer({"size":10})}
<div className="dividing-line"/>
{/* API col display area */}
{Spacer({"size":10})}
<div className="col-disp-title">すでに連携されている連携項目</div>
{Spacer({"size":20})}
{editAPI.outinfo.map((val: Defs.EditTableInfo) => {
const res: any[] = []
val.column.map((item: Defs.EditColumn, index:number) => {
res.push(
<div className={`add-col-grid col-disp-grid ${item.delete?"delete-col":item.editing?"editing-col":item.edited&&"edited-col"}`}>
<div className="add-item">
<div className="add-input-form-title"/>
<p className="adjust-indent-base adjust-indent-check">{item.required&&"✔"}</p>
</div>
<div className="add-item">
<div className="add-input-form-title"/>
<p className="adjust-indent-base adjust-indent-form">{item.logicalName}</p>
</div>
<div className="add-item">
<div className="add-input-form-title"/>
<p className="adjust-indent-base adjust-indent-form">{item.physicalName}</p>
</div>
<div className="add-item">
<div className="add-input-form-title"/>
<p className="adjust-indent-base adjust-indent-type">{item.type}</p>
</div>
<div className="btn-left">
<i className={`fa fa-solid fa-pen ${item.delete&&"not-applicable"}`}
aria-hidden="true"
onClick={()=>{
if(item.editing){
editCancel(item)
}else{
if(editIndex!==""){
editAPI.outinfo[0].column[Number(editIndex)].editing=false
}
formReset()
setColLogicalName(item.logicalName)
setColPhysicalName(item.physicalName)
setColType(item.type)
$('#'+editAPI.physical_name+'-required').prop('checked',item.required);
item.editing=true
setEditIndex(String(index))
}
}}
></i>
{Spacer({"size":50,horizontal:true})}
{deleteOrUndo(item)}
</div>
</div>
)
})
return res
})}
{Spacer({"size":30})}
<div className="dividing-line"/>
{/* Add col input area */}
{Spacer({"size":30})}
<div className="add-col-grid">
<div className="add-item">
<div className="add-input-form-title">必須</div>
<input type="checkbox" id={editAPI.physical_name+"-required"} className="form-check-input" name={editAPI.physical_name+"-required"} value="1"/>
<label className="form-check-label check-only input-data-check" htmlFor={editAPI.physical_name+"-required"}></label>
</div>
<div className="add-item">
<div className="add-input-form-title">論理名</div>
<input
type="text"
id={editAPI.physical_name+"-add-col-l-name"}
className="form-control add-input-form"
value={colLogicalName}
{...colLogicalNameField}
onChange={(e) => {
colLogicalNameField.onChange(e);
handleChange(e);
}}
/>
</div>
<div className="add-item">
<div className="add-input-form-title">物理名</div>
<input
type="text"
id={editAPI.physical_name+"-add-col-p-name"}
className="form-control add-input-form"
value={colPhysicalName}
{...colPhysicalNameField}
onChange={(e) => {
colPhysicalNameField.onChange(e);
handleChange(e);
}}
/>
</div>
<div className="add-item">
<div className="add-input-form-title">データ型</div>
<select
id={editAPI.physical_name+"-category-sel"}
className="form-control input-data-type"
required
value={colType}
{...colTypeField}
onChange={(e) => {
colTypeField.onChange(e);
selectChange(e);
}}
>
<option hidden value="">選択する</option>
{gender.map((item, i) => (
<option
value={item.key}
key={item.key}
>
{item.label}
</option>
))}
</select>
</div>
{addOrUpd()}
</div>
{Spacer({"size":25})}
<div className="error-msg-area">
{errors.colLogicalName?.message && (
<div className="text-danger">{errors.colLogicalName.message}</div>
)}
{errors.colPhysicalName?.message && (
<div className="text-danger">{errors.colPhysicalName.message}</div>
)}
{errors.colType?.message && (
<div className="text-danger">{errors.colType.message}</div>
)}
<div className="text-danger" id={editAPI.physical_name+'-error'}></div>
</div>
<div className="reflect-col-btn">
<button className="btn btn-primary"
onClick={()=>{
$("#"+editAPI.physical_name+"-dtl-modal").modal("hide").on("hidden.bs.modal",()=>{
setTimeout(()=>$("#testModal").modal("show"))
})}}>
反映する
</button>
</div>
{Spacer({"size":30})}
</div>
</div>
{/* <!-- /.modal-content --> */}
</div>
{/* <!-- /.modal-dialog --> */}
</div>
{/* <!-- /.modal --> */}
</>
)
}
export default NewAPIDtlModal;
この画面にポップアップを追加する。最終から出てくる感じで。ポップアップに「test」を表示。OKボタンで閉じれるように
import * as Defs from './metricsDefs';
function MetricsIfInfoModal({details}: {details: Defs.MetricsDetails}) {
return (
INデータ項目
OUTデータ項目
API
) }
function IfData({table}: {table: Defs.TableInfo}) { return (
); }
function Api({details}: {details: Defs.MetricsDetails}) { return (
) }
export default MetricsIfInfoModal;