遅刻可視化ツール 設計書 === ###### tags: `tech` ## What's this? - 最初Vue.jsで遅刻可視化ツールを実装してみたところ, 破綻した - 型が無くて辛い - 命名規則がぐちゃぐちゃ - 複数のAPIが同じ機能を持ってしまったり, それぞれのAPIが組み合わせづらいものになってしまった - 設計書を書き, それに沿って1から開発することでこれらの問題を解決したい ## アプリの概要 - Google Location Historyを用いて通勤・通学の遅刻を判定するツール - Google Location HistoryのデータはGoogleアーカイブから取得したものを利用 - 職場などの領域にその日初めて入った時刻を到着時刻とする - 領域の設定にはGoogle Mapsを利用 - 遅刻回数, 遅刻時間などをチャートで可視化 ## APIと命名規則 どのようなAPIを組み合わせて実装するのか決める. ```typescript= // 位置情報 interface Location { date: Date; latitude: number; longitude: number; } // 日付 const date = new Date('2018/02/20 14:40'); const dateKey = '2018/02/20'; const timeKey = '14:40'; const duration = diffPureTime(date1, date2); type StartTimeMap = Map<DateKey, PureTime>; // チャート interface DateGroupable { monthKey: string; // 2018/02 weekKey: string; // 2018/02/04 - 2018/02/10 weekdayKey: string; // 月 } interface LateData extends DateGroupable { late: boolean; } interface LateTimeData extends DateGroupable { duration: Duration; } const filteredLocations: Location[] = locations // 開始時刻が設定された日付を持つLocationを抽出 .filter(({date}) => startTimeMap.has(toDateKey(date))) // 職場の位置情報を持つLocationを抽出 .filter(location => polygons.some(polygon => inPolygon(location, polygon))) // 設定された日付の範囲に含まれる日付を持つLocationを抽出 .filter(({date}) => inDateRange(date, dateRange)) // その日において最も早い到着時刻を持つLocationを抽出 .groupBy(groupByDate) .map(locations => locations.minBy(compareByTime)) // 月/週/曜日ごとにグループ分けする const groupedLocations = filteredLocations .groupBy(groupByDateGroupType); // チャートに出力するデータを生成 // mapper: (locations: Location[]) => number const mapper = createMapper(chartType); const data = groupedLocations .map((groupKey, groupValues) => ({ groupKey, groupValue: mapper(groupValues) })); render( <LineChart data={data}> <Line dataKey="groupValue"/> <XAxis dataKey="groupKey"/> </LineChart> ); ``` ## Stateの要件 - ロケーション履歴の読み込み - ロケーション履歴 - 読み込み状態 - 開始時刻の設定 - 開始時刻のマップ - Polygon Picker - ポリゴンの集合 - チャート - 遅刻率のタブ - 始業時間と到着時間の差の平均のタブ - それぞれのタブで月ごと, 曜日ごとの切り替え ## Stateの定義 ```typescript= type RootState = { config: ConfigState; chart: ChartState; }; type ConfigState = { locations: Location[]; startTimeMap: StartTimeMap; polygons: Polygon[]; }; type ChartState = { type: '遅刻回数' | '遅刻時間'; dateGroupType: '月' | '週' | '曜日'; }; ``` ## Actionの定義 ```typescript= // LocationHistoryInput actionCreator<{ locations: Location[] }>('loadLocationHistory'); // StartTimePicker actionCreator<{ startTimeMap: StartTimeMap }>('addStartTimes'); actionCreator<{ dateKeys: DateKey[] }>('removeStartTimes'); actionCreator<{ oldDateKeys: DateKey[], newStartTimeMap: StartTimeMap }>('editStartTimes'); // PolygonPicker actionCreator<{ polygons: Polygon[] }>('changePolygons'); // Chart actionCreator<{ type: ChartType }>('changeType'); actionCreator<{ dateGroupType: DateGroupType }>('changeDateGroupType'); ``` ## 依存するライブラリ・フレームワーク 要件を多く満たすものを各カテゴリの中から1つ以上選ぶ. 満たす要件が少ない, 安定していない, 巨大, 複雑度が増す, 導入が困難などコストが見合わないと判断した場合は何も選ばない. - [x] Viewライブラリ - 要件 - State to View - Virtual DOM - TypeScriptのサポート - 選択肢 - [x] React - [ ] Vue.js - [x] 状態管理ライブラリ - 要件 - 状態の分割 - デバッガによるアクションの監視 - TypeScriptのサポート - 選択肢 - [x] Redux - [ ] MobX - [ ] なし - [x] UIフレームワーク - 要件 - Grid Layout - Form - DatePicker - TimePicker - ソート可能なテーブル - フィルタリング可能なテーブル - 巨大なテーブルの高速なレンダリング - TypeScriptのサポート - 選択肢 - [ ] mui-org/material-ui - [x] ant-design/ant-design - TypeScriptのサポートが充実している - 欲しいコンポーネントが一通り揃っている - デザインもナウい感じで良い - [ ] eleme/element-react - 開発が止まっている? - [ ] bvaughn/react-virtualized - [x] コレクションライブラリ - 要件 - コレクションに対する高速な操作 - immutableな操作 - `sortBy`, `minBy`, `groupBy` のサポート - TypeScriptのサポート - 選択肢 - [x] Immutable.js - [ ] Underscorejs - [x] 日時ライブラリ - 要件 - 日付, 時間を表現できること - 時間の差を表現できること - `inDateRange`, `diffTime` があること - 日時の扱いが簡単なこと - 軽量なこと - 選択肢 - [ ] Moment.js - [ ] Luxon - [x] date-fns/date-fns - [x] マップコンポーネント - 要件 - Google Mapsが利用できること - Autocomplete, GPSが利用できること - Polygonを扱えること - UIフレームワークとの結合が容易 - 選択肢 - [x] tomchentw/react-google-maps - [ ] 生のGoogle Maps JavaScript API - [x] チャートコンポーネント - 要件 - 利用が簡単なこと - x, y軸のデータをdataオブジェクトのキーで指定できること - 選択肢 - [x] recharts/recharts - [ ] FormidableLabs/victory