タスクやプロジェクトの管理でkintoneを利用している方も多いと思います。
ガントチャートとカンバンの機能でkintoneのデータを可視化できたら、より効率的にタスクを管理できます。
今回の記事はReactを使って、ガントチャートとカンバンをkintoneに表示するカスタマイズを紹介します。
デモ環境で実際に動作を確認できます。
https://dev-demo.cybozu.com/k/339/
ログイン情報は
cybozu developer networkデモ環境
で確認してください。
今回のカスタマイズを適用することで、kintoneに登録したタスクのデータを、ガントチャートとカンバンの形式で表示させることができます。
タスクをガントチャート形式で表示
固定リンクがコピーされました
レコード一覧画面では、すべてのタスクを表示します。
レコード詳細画面では、関連する親子タスクを表示します。
ガントチャートで表示したときの機能の詳細は、次のとおりです。
- タスクの「タイトル」「開始日付」「完了日付」などの情報をチャートに表示します。
- タスクの「タイプ」によって、異なる色で各タスクのチャートを表示します。
- 「親タスクID」フィールドの登録で、タスクの親子関係を設定できます。親子関係はガントチャートの矢印で表示されます。
- タスクのチャートをドラッグすることで、タスクの「開始日付」「完了日付」を変更できます。
- タスクのチャートをダブルクリックした場合、該当タスクのレコード詳細画面に遷移します。
タスクをカンバン形式で表示
固定リンクがコピーされました
レコード一覧画面では、すべてのタスクを表示します。
カンバンで表示したときの機能の詳細は、次のとおりです。
- タスクの「タイトル」「担当者」「開始日付」「完了日付」「タイプ」などの情報をカンバンのカードに表示します。
- 「ステータス」ごとに列を分けて、タスクを表示します。
- タスクのカードをドラッグすることで、タスクの「ステータス」(列)を変更できます。
- タスクの「タイプ」によって、異なる色で各タスクのラベルを表示します。
- タスクのカードをクリックした場合、該当タスクのレコード詳細画面に遷移します。
下準備として、まずはガントチャートとカンバンを表示するための「タスク管理」アプリを作成しましょう。
「はじめから作成」でアプリを新規作成し、次のすべてのフィールドをアプリに追加してください。
フィールドの種類 |
フィールド名 |
フィールドコード |
備考 |
ドロップダウン |
Type |
type |
タスクのタイプ |
ドロップダウン |
Priority |
priority |
タスクの優先度 |
ドロップダウン |
Status |
status |
タスクのステータス(カンバンの列) |
日付 |
Start |
startDate |
タスクの開始時間 |
日付 |
End |
endDate |
タスクの完了時間 |
ユーザー選択 |
Assignee |
assignee |
タスクの担当者 |
文字列(1行) |
Summary |
summary |
タスクのタイトル |
文字列 (複数行) |
Detail |
detail |
詳細情報 |
数値 |
Parent Task |
parent |
親タスクID |
関連レコード一覧 |
Subtasks |
subtasks |
同じ親タスクのレコードを表示します。- 参照するアプリ:このアプリ
- 表示するレコードの条件:
Parent Task=Parent Task - 表示するフィールド:レコード番号、Summary、Assignee
|
スペース |
|
addSub |
子タスク 追加ボタン |
サンプルコードは、GitHubに公開されています。
SAMPLE-ganttchart-kanban
サンプルコードのリポジトリをcloneし、「SAMPLE-ganttchart-kanban」フォルダーで次のコマンドを実行してください。
1
2
|
npm install
npm run build
|
コマンドを実行したら、「dist」フォルダーの中にファイルが生成されます。
今回のカスタマイズの適用には、この4つのファイルを利用します。
「タスク管理」アプリの設定画面を開き、[設定]タブの[JavaScript / CSSでカスタマイズ]をクリックします。
以下の画像を参考に、先ほど生成されたJavaScript、CSSファイルを「PC用のJavaScriptファイル」、「PC用のCSSファイル」からアップロードします。
設定を保存したら、アプリを更新します。
次のサンプルレコードのように、ガントチャートとカンバンに表示させたいデータを「タスク管理」アプリに登録します。
レコードを登録したら、レコードの一覧画面と詳細画面を表示します。
完成イメージ
のようにガントチャート、カンバンが表示されれば成功です!
サンプルコードの解説
固定リンクがコピーされました
サンプルコードの解説をしていきます。
今回のカスタマイズは、kintoneアプリにガントチャートとカンバンを表示する処理に加え、該当アプリのデータの取得/更新、表示を切り替えるボタンの生成などの処理があります。
今回のプログラムはReactとTypeScriptを利用しています。
ReactとTypeScriptの使い方について確認したい方は、次の記事を合わせて確認してください。
アプリのデータを取得/更新する処理、ボタンを生成する処理
固定リンクがコピーされました
KintoneAppRepository.tsx
実際にkintoneからレコードを取得、更新する処理を定義しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
/*
* react sample program
* Copyright (c) 2022 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/
import {KintoneRestAPIClient, KintoneRecordField, KintoneFormFieldProperty} from '@kintone/rest-api-client';
import stc from 'string-to-color';
// タスク管理アプリの型を定義する
export type AppRecord = {
$id: KintoneRecordField.ID
parent: KintoneRecordField.Number
summary: KintoneRecordField.SingleLineText
detail: KintoneRecordField.MultiLineText
assignee: KintoneRecordField.UserSelect
startDate: KintoneRecordField.Date
endDate: KintoneRecordField.Date
type: KintoneRecordField.Dropdown
status: KintoneRecordField.Dropdown
}
export type AppProperty = {
type: KintoneFormFieldProperty.Dropdown
status: KintoneFormFieldProperty.Dropdown
}
// レコードを取得する処理
const getRecordsByApi = (query?: string) => {
return new KintoneRestAPIClient().record.getRecords<AppRecord>({
app: kintone.app.getId()!,
query: `${query ? query : ''} order by $id asc`,
});
};
// フォームの設定情報を取得する処理
const getFieldsByApi = () => {
return new KintoneRestAPIClient().app.getFormFields<AppProperty>({app: kintone.app.getId()!});
};
// 「ステータス」フィールドの情報を更新する処理
export const updateStatus = async (recordID: string, status: string) => {
await new KintoneRestAPIClient().record.updateRecord({
app: kintone.app.getId()!,
id: recordID,
record: {
status: {
value: status,
},
},
});
};
// 「開始日付」「完了日付」フィールドの情報を更新する処理
export const updateDate = async (recordID: string, start: string, end: string) => {
await new KintoneRestAPIClient().record.updateRecord({
app: kintone.app.getId()!,
id: recordID,
record: {
startDate: {
value: start,
},
endDate: {
value: end,
},
},
});
};
// レコード、フォームの設定情報の取得を実行する処理
export const getRecords = async (
cb: (records: AppRecord[], status: Map<string, number>, type: Map<string, string>) => void,
query?: string,
) => {
const [fields, list] = await kintone.Promise.all([getFieldsByApi(), getRecordsByApi(query)]);
const type = new Map();
const status = new Map();
// タスクのタイプの情報をtypeオブジェクトにセットする
Object.keys(fields.properties.type.options).forEach((k) => {
type.set(k, stc(k));
});
// タスクのステータスの情報をstatusオブジェクトに順番にセットする
Object.keys(fields.properties.status.options).forEach((k) => {
status.set(k, fields.properties.status.options[k].index);
});
cb(list.records, status, type);
};
|
App.tsx
ガントチャート、カンバンの表示を切り替えるボタンを生成する処理です。
デフォルトでは、カンバンの画面を表示する設定になっています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
import React, {useEffect} from 'react';
import ReactDOM from 'react-dom';
import {Radio} from 'antd';
import GanttCharts from './GanttCharts';
import 'antd/dist/antd.css';
import Kanban from './Kanban';
export enum AppType {
Gantt = 'Gantt',
Board = 'Kanban',
}
// ガントチャート、カンバンを切り替えるボタンのコンポーネントを定義する
export const AppSwitcher = () => {
// ボタンを押したときのハンドラ
const onAppChange = (app: AppType) => {
switch (app) {
case AppType.Gantt:
ReactDOM.render(
// ガントチャートが選択された場合、レコード一覧のメニューの下側の空白部分の要素に、ガントチャートを表示する
<GanttCharts query={kintone.app.getQueryCondition() || undefined} />,
kintone.app.getHeaderSpaceElement(),
);
break;
default:
// デフォルトの表示として、レコード一覧のメニューの下側の空白部分の要素に、カンバンを表示する
ReactDOM.render(<Kanban />, kintone.app.getHeaderSpaceElement());
}
};
// 初回描画時に、カンバンの描画を実行する
useEffect(() => {
ReactDOM.render(<Kanban />, kintone.app.getHeaderSpaceElement());
}, []);
// 要素の定義と返却(React UI libraryのAnt Designのradio componentsを利用)
return (
<>
<Radio.Group
defaultValue={AppType.Board}
buttonStyle="solid"
size="large"
onChange={(e) => onAppChange(e.target.value)}
>
<Radio.Button value={AppType.Gantt}>{AppType.Gantt}</Radio.Button>
<Radio.Button value={AppType.Board}>{AppType.Board}</Radio.Button>
</Radio.Group>
</>
);
};
|
AddSub.tsx
子タスクのレコードを追加するボタンを生成する処理です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import React from 'react';
import {Button, Tooltip} from 'antd';
import {PlusCircleTwoTone} from '@ant-design/icons';
// 子タスクを追加するボタンのコンポーネントを定義する
const AddSub = ({id = ''}) => {
// ボタンを押したときのハンドラ
const onClick = () => {
if (id) {
const url = window.location.protocol + '//' + window.location.host + window.location.pathname + 'edit?pid=' + id;
window.location.assign(url.replaceAll('showedit', 'edit'));
}
};
// 要素の定義と返却
return (
<Tooltip title="add sub task">
<Button type="primary" shape="circle" icon={<PlusCircleTwoTone />} size="large" onClick={onClick} />
</Tooltip>
);
};
export default AddSub;
|
index.tsx
画面にボタンやコンポーネントを表示します。
- レコード一覧画面
- レコード詳細画面
- 開いたレコードに関連する、親子タスクのガントチャート
- 子タスクを追加するボタン
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
/*
* react sample program
* Copyright (c) 2022 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/
import React from 'react';
import ReactDOM from 'react-dom';
import {AppSwitcher} from './components/App';
import GanttCharts from './components/GanttCharts';
import AddSub from './components/AddSub';
interface KintoneEvent {
record: kintone.types.SavedFields
}
// レコード一覧のメニューの右側の空白部分の要素に、ガントチャート、カンバンを切り替えるボタンを表示する
kintone.events.on('app.record.index.show', (event: KintoneEvent) => {
ReactDOM.render(<AppSwitcher />, kintone.app.getHeaderMenuSpaceElement());
return event;
});
// レコード詳細画面のメニューの上側の空白部分の要素に、該当レコードの関連する親子タスクのガントチャートを表示する
kintone.events.on('app.record.detail.show', (event: KintoneEvent) => {
let query = `parent = ${event.record.$id.value} or $id= ${event.record.$id.value}`;
event.record.parent.value && (query += ` or $id = ${event.record.parent.value}`);
ReactDOM.render(<GanttCharts query={query} />, kintone.app.record.getHeaderMenuSpaceElement());
// スペースフィールド「addSub」に、子タスクを追加するボタンを表示する
ReactDOM.render(<AddSub id={event.record.$id.value} />, kintone.app.record.getSpaceElement('addSub'));
return event;
});
// スペースフィールド「addSub」に設置した「add sub task」ボタンをクリックして、レコード追加画面を開いた場合、
// 親タスクのレコードidを、追加された子タスクのレコードの「親タスクID」フィールドに指定する
kintone.events.on('app.record.create.show', (event: KintoneEvent) => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const pid = urlParams.get('pid');
if (pid) {
event.record.parent.value = pid;
}
return event;
});
|
ガントチャートとカンバンを生成する処理
固定リンクがコピーされました
GanttCharts.tsx
ガントチャートで表示するときのコンポーネントを定義しています。
このコンポーネントでは、次の処理ができます。
- レコードのデータをガントチャートで表示する。
- タスクをドラッグ&ドロップして、「開始日付」「完了日付」を更新する。
- タスクをダブルクリックして、レコードの詳細画面に遷移する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
/*
* echarts sample program
* Copyright (c) 2022 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/
import React, {useEffect} from 'react';
import 'gantt-task-react/dist/index.css';
import {Task, ViewMode, Gantt} from 'gantt-task-react';
import {ViewSwitcher} from './GanttViewSwitcher';
import {AppRecord, getRecords, updateDate} from '../KintoneAppRepository';
import {Spin, Result} from 'antd';
import './app.css';
// ガントチャートのコンポーネントを定義する
const GanttCharts = ({query = ''}) => {
// ガントチャートのview、tasks、画面のローディング状態の情報を保管する
const [view, setView] = React.useState<ViewMode>(ViewMode.Day);
const [tasks, setTasks] = React.useState<Task[]>();
const [isLoading, setLoading] = React.useState<boolean>(true);
// 各ViewModeのガントチャートのcolumnの幅を指定する
let columnWidth = 60;
if (view === ViewMode.Month) {
columnWidth = 300;
} else if (view === ViewMode.Week) {
columnWidth = 250;
}
// ガントチャートから、タスクの「開始日付」「完了日付」を調整した場合、変更された日付をレコードに更新する
const onTaskChange = (task: Task) => {
const start = new Date(task.start.getTime() - task.start.getTimezoneOffset() * 60 * 1000)
.toISOString()
.split('T')[0];
const end = new Date(task.end.getTime() - task.end.getTimezoneOffset() * 60 * 1000).toISOString().split('T')[0];
updateDate(task.id, start, end);
};
// ガントチャートからタスクをダブルクリックするとき、タスクの詳細画面に遷移する
const onDblClick = (task: Task) => {
const url =
window.location.protocol + '//' + window.location.host + window.location.pathname + 'show#record=' + task.id;
window.location.assign(url.replaceAll('showshow', 'show'));
};
// 初回描画時に、ガントチャートに表示するためのレコードを取得する
useEffect(() => {
getRecords(display, query);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const display = (records: AppRecord[], status: Map<string, number>, type: Map<string, string>) => {
// 取得したレコードが0件の場合、画面のローディングを停止する
if (records.length === 0) {
setLoading(false);
} else {
// 0件以上のレコードを取得した場合、取得したデータをガントチャート表示用に整形して返却する
setTasks(
records.map<Task>((record) => {
return {
id: record.$id.value,
name: record.summary.value,
start: new Date(record.startDate.value! + 'T00:00:00.000+09:00'),
end: new Date(record.endDate.value! + 'T23:59:59.000+09:00'),
progress: Math.ceil(((status.get(record.status.value!) || 0) * 100) / Math.max(status.size - 1, 1)),
styles: {progressColor: type.get(record.type.value!) || '#ff9e0d', progressSelectedColor: '#ff9e0d'},
dependencies: record.parent.value ? [record.parent.value] : [],
// isDisabled: true,
};
}),
);
}
};
let content;
// 0件以上のレコードを取得できた場合に表示する、ガントチャートの要素を定義する
if (tasks && tasks.length > 0) {
content = (
<div>
<ViewSwitcher onViewModeChange={(viewMode) => setView(viewMode)} />
<Gantt
tasks={tasks}
viewMode={view}
onDateChange={onTaskChange}
onDoubleClick={onDblClick}
listCellWidth="155px"
columnWidth={columnWidth}
todayColor="#FCFF19"
/>
</div>
);
// レコードのデータを取得できるまでに表示する、ローディング画面の要素を定義する
} else if (isLoading) {
content = (
<div className="center">
<Spin size="large" tip="Loading..." />
</div>
);
// レコードのデータを取得できなかった場合に表示する、「no data」画面の要素を定義する
} else {
content = <Result title="No data" />;
}
// content要素を返却する
return <>{content}</>;
};
export default GanttCharts;
|
GanttViewSwitcher.tsx
ガントチャートのViewModeを調整するラジオボタンを生成する処理です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
/*
* echarts sample program
* Copyright (c) 2022 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/
import React from 'react';
import {ViewMode} from 'gantt-task-react';
import {Radio} from 'antd';
import 'antd/dist/antd.css';
type ViewSwitcherProps = {
onViewModeChange: (viewMode: ViewMode) => void
}
// ガントチャートのViewMode(Quarter Day, Half Day, Day, Week, Month)を調整するラジオボタンのコンポーネントを定義する
export const ViewSwitcher: React.FunctionComponent<ViewSwitcherProps> = ({onViewModeChange}) => {
return (
<>
<Radio.Group defaultValue={ViewMode.Day} buttonStyle="solid" onChange={(e) => onViewModeChange(e.target.value)}>
<Radio.Button value={ViewMode.QuarterDay}>{ViewMode.QuarterDay}</Radio.Button>
<Radio.Button value={ViewMode.HalfDay}>{ViewMode.HalfDay}</Radio.Button>
<Radio.Button value={ViewMode.Day}>{ViewMode.Day}</Radio.Button>
<Radio.Button value={ViewMode.Week}>{ViewMode.Week}</Radio.Button>
<Radio.Button value={ViewMode.Month}>{ViewMode.Month}</Radio.Button>
</Radio.Group>
</>
);
};
|
Kanban.tsx
カンバンで表示するときのコンポーネントを定義しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
/*
* echarts sample program
* Copyright (c) 2022 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/
import React, {useEffect} from 'react';
import Board from 'react-trello';
import {AppRecord, getRecords, updateStatus} from '../KintoneAppRepository';
import {AntdCard, KCard} from './Card';
import {Spin, Result} from 'antd';
import './app.css';
// カンバンのコンポーネントを定義する
const Kanban = () => {
// カンバンに表示するレコードのデータ、画面のローディング状態の情報を保管する
const [data, setData] = React.useState<ReactTrello.BoardData>();
const [isLoading, setLoading] = React.useState<boolean>(true);
const display = (records: AppRecord[], status: Map<string, number>, type: Map<string, string>) => {
// 取得したレコードが0件の場合、画面のローディングを停止する
if (records.length === 0) {
setLoading(false);
} else {
// 0件以上のレコードを取得した場合、カンバンのカードを配置するlaneの配列を生成する
const lanes = new Array(status.size);
// 取得したステータスごとに、laneをセットする
status.forEach((v, k) => {
lanes[v] = {
id: k,
title: k,
cards: new Array<KCard>(),
};
});
// 取得したデータをカンバンのカード表示用に整形し、該当ステータスのlaneに渡す
records.forEach((record) =>
lanes[status.get(record.status.value!)!].cards!.push({
id: record.$id.value,
title: record.summary.value,
label: record.type.value!,
labelColor: type.get(record.type.value!),
description: record.detail.value,
assignee: record.assignee,
startDate: record.startDate.value!,
endDate: record.endDate.value!,
}),
);
// 整形済みのデータを返却する
setData({lanes});
}
};
// 初回描画時の処理
useEffect(() => {
// カンバンに表示するためにレコードを取得し、取得したデータを処理する
getRecords(display, kintone.app.getQueryCondition() || undefined);
}, []);
// カンバンのカードがクリックされた場合、該当カードのレコードの詳細画面を開く処理
const onCardClick = (cardId: string) => {
const url =
window.location.protocol + '//' + window.location.host + window.location.pathname + 'show#record=' + cardId;
window.location.assign(url);
};
// カードがドラッグされた場合、ドラッグの動作が完了後、レコードのステータスを更新する処理
const handleDragEnd = (cardId: string, _sourceLandId: string, targetLaneId: string) => {
updateStatus(cardId, targetLaneId);
};
let content;
// レコードのデータを取得できた場合に表示する、カンバンの要素を定義する
if (data) {
content = (
<Board
data={data}
draggable
onCardClick={onCardClick}
hideCardDeleteIcon
handleDragEnd={handleDragEnd}
style={{padding: '30px 20px', backgroundColor: '#5F9AF8'}}
components={{Card: AntdCard}}
/>
);
// レコードのデータを取得できるまでに表示する、ローディング画面の要素を定義する
} else if (isLoading) {
content = (
<div className="center">
<Spin size="large" tip="Loading..." />
</div>
);
// レコードのデータを取得できなかった場合に表示する、「no data」画面の要素を定義する
} else {
content = <Result title="No data" />;
}
// content要素を返却する
return <>{content}</>;
};
export default Kanban;
|
このコンポーネントでは、次の処理ができます。
- カンバンのカードをドラッグ&ドロップして、ステータスを更新する。
- カードをクリックして、レコードの詳細画面に遷移する。
Card.tsx
カンバンのカードを生成する処理と、各タスクの担当者の情報をカードに表示する処理です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
/*
* echarts sample program
* Copyright (c) 2022 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/
import {KintoneRecordField} from '@kintone/rest-api-client';
import {Card, Avatar, Tag} from 'antd';
import React from 'react';
const {Meta} = Card;
export interface KCard extends ReactTrello.DraggableCard {
labelColor?: string
assignee: KintoneRecordField.UserSelect
startDate: string
endDate: string
onClick?: () => void
}
// カンバンのカードに表示する、タスク担当者のコンポーネントを定義する
const Avatars = (props: { assignee: KintoneRecordField.UserSelect }) => {
// 要素の定義と返却
return (
<Avatar.Group
maxCount={2}
size="large"
maxStyle={{
color: '#f56a00',
backgroundColor: '#fde3cf',
}}
>
{props.assignee.value.map((element) => {
return (
<Avatar
// src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
style={{
backgroundColor: '#15dad2',
}}
key={element.code}
>
{element.name}
</Avatar>
);
})}
</Avatar.Group>
);
};
// カンバンのカードのコンポーネントを定義する
export const AntdCard = (props: KCard) => {
// 要素の定義と返却(React UI libraryのAnt Designのcard componentsを利用)
return (
<Card
extra={<Tag color={props.labelColor}>{props.label}</Tag>}
style={{width: 300}}
title={props.title}
onClick={props.onClick}
>
<Meta
avatar={<Avatars assignee={props.assignee} />}
title={`${props.startDate.substring(5)}~${props.endDate.substring(5)}`}
description={props.description}
/>
</Card>
);
};
|
今回の記事は、Reactを使ってガントチャートとカンバンをkintoneに表示するカスタマイズを紹介しました。
統計ダッシュボードや、レーダーチャートなどをkintoneに表示するカスタマイズ
を紹介する記事もありますので、興味ある方はぜひ確認してみてください。
この記事で利用しているライブラリ
固定リンクがコピーされました