Skip to content

Commit bb0d663

Browse files
committed
Implement web front
1 parent 93712f7 commit bb0d663

15 files changed

+4731
-0
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
node_modules
3+
yarn-error.log
4+
dist
5+
.cache
6+
.vscode
7+
.parcel-cache

package.json

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "zengin-code.github.io",
3+
"version": "1.0.0",
4+
"description": "https://zengin-code.github.io/",
5+
"main": "dist/index.html",
6+
"repository": "[email protected]:zengin-code/zengin-code.github.io.git",
7+
"author": "Sho Kusano <[email protected]>",
8+
"license": "MIT",
9+
"private": true,
10+
"scripts": {
11+
"build": "webpack",
12+
"serve": "webpack serve"
13+
},
14+
"dependencies": {
15+
"@fortawesome/fontawesome-free": "^5.15.1",
16+
"bulma": "^0.9.1",
17+
"react": "^16.8.0",
18+
"react-dom": "^16.8.0",
19+
"react-window": "^1.8.6",
20+
"tslib": "^2.0.3",
21+
"window-table": "^1.0.0-alpha.11"
22+
},
23+
"devDependencies": {
24+
"@types/node": "^14.14.13",
25+
"@types/react": "^17.0.0",
26+
"@types/react-dom": "^17.0.0",
27+
"copy-webpack-plugin": "^7.0.0",
28+
"css-loader": "^5.0.1",
29+
"file-loader": "^6.2.0",
30+
"html-webpack-plugin": "^4.5.0",
31+
"json-loader": "^0.5.7",
32+
"sass": "^1.30.0",
33+
"sass-loader": "^10.1.0",
34+
"style-loader": "^2.0.0",
35+
"terser-webpack-plugin": "^5.0.3",
36+
"ts-loader": "^8.0.12",
37+
"typescript": "^4.1.3",
38+
"webpack": "^5.10.1",
39+
"webpack-cli": "^4.2.0",
40+
"webpack-dev-server": "^3.11.0"
41+
}
42+
}

src/app.tsx

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import '@fortawesome/fontawesome-free/scss/fontawesome.scss';
2+
import '@fortawesome/fontawesome-free/scss/regular.scss';
3+
import '@fortawesome/fontawesome-free/scss/solid.scss';
4+
import '@fortawesome/fontawesome-free/scss/brands.scss';
5+
import 'bulma/bulma.sass';
6+
7+
import React, { FunctionComponent } from "react";
8+
import { Header } from "./header";
9+
import { Hero } from './hero';
10+
import { Search } from './search';
11+
12+
export const App: FunctionComponent = () => {
13+
return (
14+
<>
15+
<Header />
16+
<Hero />
17+
18+
<Search />
19+
</>
20+
)
21+
}

src/banks.tsx

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import React, { FunctionComponent, HTMLAttributes, useEffect, useState } from "react";
2+
import { Html5Table, useDebouncedState, createFilter, useFilter } from 'window-table';
3+
import { Loading } from "./loading";
4+
import { useSearchContext } from "./searchContext";
5+
6+
interface BankData {
7+
readonly code: string;
8+
readonly name: string;
9+
readonly kana: string;
10+
readonly hira: string;
11+
readonly roma: string;
12+
}
13+
14+
interface State {
15+
loading: boolean;
16+
banks: BankData[];
17+
}
18+
19+
const filter = createFilter(['code', 'name', 'kana', 'hira', 'roma'])
20+
21+
const Row: FunctionComponent<HTMLAttributes<HTMLTableRowElement> & { row: BankData }> = ({ row, className, ...props }) => {
22+
const ctx = useSearchContext();
23+
24+
return (
25+
<tr
26+
{...props}
27+
onClick={() => {
28+
ctx.change(row.code);
29+
}}
30+
className={`${className} ${row.code === ctx.code ? 'is-selected' : ''}`}
31+
/>
32+
);
33+
}
34+
35+
export const Banks: FunctionComponent = () => {
36+
const [state, change] = useState<State>({ loading: false, banks: [] });
37+
const [text, debouncedText, setText] = useDebouncedState('');
38+
39+
useEffect(() => {
40+
change({ ...state, loading: true });
41+
fetch('/api/banks.json')
42+
.then((res) => res.json())
43+
.then((banks: { [key: string]: BankData}) => {
44+
return Object.values(banks).sort((a, b) => parseInt(a.code, 10) - parseInt(b.code, 10))
45+
})
46+
.then((banks) => {
47+
change({ ...state, banks, loading: false })
48+
})
49+
.catch(() => {
50+
change({ ...state, loading: false });
51+
})
52+
}, [])
53+
54+
const banks = useFilter(filter, state.banks, debouncedText) as BankData[];
55+
56+
if (state.loading) {
57+
return <Loading message="Loading banks..." />;
58+
}
59+
60+
return (
61+
<div className="section">
62+
<form className="form mb-3">
63+
<div className="field">
64+
<div className="control has-icons-left">
65+
<input
66+
type="search"
67+
className="input"
68+
placeholder="ex: 0005 or みつびし or mitsubishi or 三菱"
69+
value={text}
70+
onChange={(e) => setText(e.target.value)}
71+
/>
72+
<span className="icon is-left">
73+
<i className="fas fa-search" />
74+
</span>
75+
</div>
76+
</div>
77+
</form>
78+
<Html5Table
79+
columns={[
80+
{ key: 'code', title: 'Code', width: 1 },
81+
{ key: 'name', title: 'Name', width: 2 },
82+
]}
83+
data={banks}
84+
className="is-fullwidth"
85+
style={{ height: 'min(30rem,100vh)', overflow: 'hidden' }}
86+
Row={Row}
87+
/>
88+
<div className="notification is-info is-light mt-3">
89+
<p>This data by <a href="/api/banks.json">https://zengin-code.github.io/api/banks.json</a> .</p>
90+
<p>You can use this JSON like API.</p>
91+
</div>
92+
</div>
93+
)
94+
}

src/branches.tsx

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import React, { FunctionComponent, useEffect, useState } from "react";
2+
import { Html5Table, useDebouncedState, createFilter, useFilter } from 'window-table';
3+
import { Loading } from "./loading";
4+
5+
interface BranchData {
6+
readonly code: string;
7+
readonly name: string;
8+
readonly kana: string;
9+
readonly hira: string;
10+
readonly roma: string;
11+
}
12+
13+
interface State {
14+
loading: boolean;
15+
branches: BranchData[];
16+
invalid: boolean;
17+
}
18+
19+
const filter = createFilter(['code', 'name', 'kana', 'hira', 'roma'])
20+
21+
export const Branches: FunctionComponent<{ code: string }> = ({ code }) => {
22+
const [state, change] = useState<State>({ loading: false, invalid: true, branches: [] });
23+
const [text, debouncedText, setText] = useDebouncedState('');
24+
25+
useEffect(() => {
26+
if (!code) {
27+
change({ ...state, loading: false, branches: [], invalid: true });
28+
return
29+
}
30+
31+
change({ ...state, loading: true, invalid: false });
32+
33+
fetch(`/api/branches/${code}.json`)
34+
.then((res) => res.json())
35+
.then((branches: { [key: string]: BranchData}) => {
36+
return Object.values(branches).sort((a, b) => parseInt(a.code, 10) - parseInt(b.code, 10))
37+
})
38+
.then((branches) => {
39+
change({ ...state, branches, loading: false, invalid: false });
40+
})
41+
.catch(() => {
42+
change({ ...state, loading: false, invalid: true });
43+
});
44+
}, [code])
45+
46+
const branches = useFilter(filter, state.branches, debouncedText) as BranchData[];
47+
48+
if (state.loading) {
49+
return <Loading message="Loading branches..." />;
50+
}
51+
52+
return (
53+
<div className="section">
54+
{state.invalid ? (
55+
<div className="notification has-text-centered">
56+
<p>Select a bank</p>
57+
</div>
58+
) : (
59+
<>
60+
<form className="form mb-3">
61+
<div className="field">
62+
<div className="control has-icons-left">
63+
<input
64+
type="search"
65+
className="input"
66+
placeholder="ex: 0001 or ginza or ぎんざ or 銀座"
67+
value={text}
68+
onChange={(e) => setText(e.target.value)}
69+
/>
70+
<span className="icon is-left">
71+
<i className="fas fa-search" />
72+
</span>
73+
</div>
74+
</div>
75+
</form>
76+
<Html5Table
77+
columns={[
78+
{ key: 'code', title: 'Code', width: 1 },
79+
{ key: 'name', title: 'Name', width: 2 },
80+
]}
81+
data={branches}
82+
className="is-fullwidth"
83+
style={{ height: 'min(30rem,100vh)', overflow: 'hidden' }}
84+
/>
85+
<div className="notification is-info is-light mt-3">
86+
<p>This data by <a href={`/api/branches/${code}.json`}>https://zengin-code.github.io/api/branches/{code}.json</a> .</p>
87+
<p>You can use this JSON like API.</p>
88+
</div>
89+
</>
90+
)}
91+
</div>
92+
)
93+
}

src/header.tsx

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React, { FunctionComponent, useCallback, useState } from "react";
2+
3+
export const Header: FunctionComponent = () => {
4+
const [state, change] = useState({
5+
burger: false,
6+
});
7+
8+
const burgerCallback = useCallback((e) => {
9+
e.preventDefault();
10+
change({ ...state, burger: !state.burger });
11+
}, [state.burger])
12+
13+
return (
14+
<div className="navbar">
15+
<div className="container">
16+
<div className="navbar-brand">
17+
<div className="navbar-item">
18+
<a className="button is-light" href="/">
19+
<span>Zengin Code</span>
20+
<span className="is-hidden-mobile">&nbsp;/ 統一金融機関コード</span>
21+
</a>
22+
</div>
23+
24+
<a role="button" className="navbar-burger" aria-label="menu" aria-expanded="false" onClick={burgerCallback}>
25+
<span aria-hidden="true"></span>
26+
<span aria-hidden="true"></span>
27+
<span aria-hidden="true"></span>
28+
</a>
29+
</div>
30+
31+
<div className={`navbar-menu ${state.burger ? 'is-active' : ''}`}>
32+
<nav className="navbar-end">
33+
<div className="navbar-item">
34+
<a className="button is-flex-mobile is-link is-light" href="https://github.com/zengin-code" aria-label="GitHub">
35+
<span className="icon">
36+
<i className="fab fa-fw fa-github" />
37+
</span>
38+
<span>GitHub</span>
39+
</a>
40+
</div>
41+
</nav>
42+
</div>
43+
</div>
44+
</div>
45+
);
46+
};

src/hero.tsx

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { FunctionComponent, useState } from "react";
2+
3+
const INSTALLATION_CODES = {
4+
rb: 'gem install zengin_code',
5+
js: 'npm install zengin-code',
6+
py: 'pip install zengin_code',
7+
};
8+
9+
const USAGE_CODES: typeof INSTALLATION_CODES = {
10+
rb: "require 'zengin_code'\n\nZenginCode::Bank.all",
11+
js: "const zenginCode = require('zengin-code');\n\nzenginCode['0001'];",
12+
py: 'from zengin_code import Bank\n\nBank.all',
13+
};
14+
15+
interface State {
16+
language: keyof typeof INSTALLATION_CODES;
17+
}
18+
19+
export const Hero: FunctionComponent = () => {
20+
const [state, change] = useState<State>({ language: 'rb' });
21+
const installationCode = INSTALLATION_CODES[state.language];
22+
const usageCode = USAGE_CODES[state.language];
23+
24+
return (
25+
<div className="hero">
26+
<div className="hero-body">
27+
<div className="container">
28+
<div className="columns">
29+
<div className="column">
30+
<h1 className="title">統一金融機関コードデータセット</h1>
31+
<p className="subtitle">Zengin Code はプログラム上で統一金融機関コードを手軽に読み込める、データセットライブラリです。</p>
32+
</div>
33+
<div className="column">
34+
<div className="tabs is-toggle is-centered is-small">
35+
<ul>
36+
<li className={state.language === 'rb' ? 'is-active' : ''}><a onClick={(e) => {e.preventDefault(); change({ ...state, language: 'rb' })}} href="#">Ruby</a></li>
37+
<li className={state.language === 'js' ? 'is-active' : ''}><a onClick={(e) => {e.preventDefault(); change({ ...state, language: 'js' })}} href="#">JavaScript</a></li>
38+
<li className={state.language === 'py' ? 'is-active' : ''}><a onClick={(e) => {e.preventDefault(); change({ ...state, language: 'py' })}} href="#">Python</a></li>
39+
</ul>
40+
</div>
41+
<div className="content">
42+
<h5>Installation</h5>
43+
<pre><code className="is-family-monospace">{installationCode}</code></pre>
44+
<h5>Usage</h5>
45+
<pre><code className="is-family-monospace">{usageCode}</code></pre>
46+
47+
<div className="has-text-right">
48+
<a className="button is-link is-light" href={`https://github.com/zengin-code/zengin-${state.language}`}>See more</a>
49+
</div>
50+
</div>
51+
</div>
52+
</div>
53+
</div>
54+
</div>
55+
</div>
56+
)
57+
}

src/index.html

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<%= htmlWebpackPlugin.tags.headTags %>
7+
<title>Zengin Code / 統一金融機関コードデータセット</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
</body>
12+
</html>

src/index.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import React from 'react';
2+
import { render } from 'react-dom';
3+
import { App } from './app';
4+
5+
const container = document.getElementById('app');
6+
render(<App />, container);

0 commit comments

Comments
 (0)