refactor: StockList

This commit is contained in:
monoid 2023-07-28 17:52:07 +09:00
parent ea611f0cdc
commit 454850c6b3
3 changed files with 169 additions and 127 deletions

View File

@ -1,9 +1,18 @@
import { Button } from "../components/Button.tsx"; import { Button } from "../components/Button.tsx";
import { useEffect, useRef, useLayoutEffect } from "preact/hooks"; import { useEffect, useLayoutEffect, useRef } from "preact/hooks";
import { ComponentChildren } from "preact"; import { ComponentChildren } from "preact";
import { Signal, useSignal } from "@preact/signals"; import { Signal, useSignal } from "@preact/signals";
import { IS_BROWSER } from "$fresh/runtime.ts"; import { IS_BROWSER } from "$fresh/runtime.ts";
import { mapValues } from "$std/collections/map_values.ts"; import { mapValues } from "$std/collections/map_values.ts";
import { useAsync } from "../util/util.ts";
import {
Coperation,
CorpSimple,
fetchKosdaqList,
fetchKospiList,
fetchPageInfo,
PageCorpsInfo,
} from "../util/api.ts";
interface StockProps { interface StockProps {
pageName: string; pageName: string;
@ -31,118 +40,70 @@ function ToggleButton(props: ToggleButtonProps) {
); );
} }
type QueryStatus<T> = { function StockListByDate(
type: "loading"; { prevSet, rows, name }: {
} | { prevSet: Set<string>;
type: "complete"; rows: Coperation[];
data: T; name: string;
} | { },
type: "error"; ) {
err: Error;
};
function useAsync<T>(fn: () => Promise<T>): Signal<QueryStatus<T>> {
const state = useSignal({
type: "loading",
} as QueryStatus<T>);
useEffect(() => {
(async () => {
try {
const data = await fn();
state.value = {
type: "complete",
data: data,
};
} catch (err) {
state.value = {
type: "error",
err: err,
};
}
})();
}, []);
return state;
}
interface Coperation {
Name: string;
Code: string;
Sector: string;
Product: string;
ListingDay: string;
ClosingMonth: string;
Representative: string;
Homepage: string;
AddressArea: string;
LastUpdate: string;
}
interface PageCorpsInfo {
name: string;
description: string;
corpListByDate: Record<string, Coperation[]>;
}
interface CorpSimple {
code: string;
name: string;
}
function StockListByDate({prevSet, rows, name}:{prevSet:Set<string>,
rows:Coperation[],
name: string}){
const lastCount = useRef(rows.length); const lastCount = useRef(rows.length);
const curCount = rows.length; const curCount = rows.length;
const parent = useRef<HTMLDivElement>(null); const parent = useRef<HTMLDivElement>(null);
const controller = useRef<{ const controller = useRef<
isEnabled: ()=> boolean, {
disable: ()=>void, isEnabled: () => boolean;
enable: ()=> void disable: () => void;
}| undefined>(); enable: () => void;
useEffect(()=>{ } | undefined
(async ()=>{ >();
console.log("animation mount on ",name); useEffect(() => {
const {default:autoAnimate} = await import("https://esm.sh/@formkit/auto-animate@0.7.0"); (async () => {
if (parent.current){ console.log("animation mount on ", name);
const { default: autoAnimate } = await import(
"https://esm.sh/@formkit/auto-animate@0.7.0"
);
if (parent.current) {
const cntr = autoAnimate(parent.current); const cntr = autoAnimate(parent.current);
controller.current = cntr; controller.current = cntr;
} }
})(); })();
},[parent]); }, [parent]);
useLayoutEffect(()=>{ useLayoutEffect(() => {
if (controller.current){ if (controller.current) {
if (Math.abs(curCount - lastCount.current) > 200){ if (Math.abs(curCount - lastCount.current) > 200) {
console.log('disable animation', curCount, "from", lastCount.current); console.log("disable animation", curCount, "from", lastCount.current);
controller.current.disable(); controller.current.disable();
} } else {
else { console.log("enable animation", curCount, "from", lastCount.current);
console.log('enable animation', curCount, "from", lastCount.current);
controller.current.enable(); controller.current.enable();
} }
lastCount.current = curCount; lastCount.current = curCount;
} }
}, [parent, rows]); }, [parent, rows]);
return <div ref={parent}> return (
<h2 class="text-lg">{name}</h2> <div ref={parent}>
{rows.map((row) => { <h2 class="text-lg">{name}</h2>
const firstOccur = !prevSet.has(row.Code); {rows.map((row) => {
return ( const firstOccur = !prevSet.has(row.Code);
<div return (
key={row.Code} <div
class={[ key={row.Code}
"bg-white", class={[
firstOccur ? "text-[#ff5454] underline" : "text-black", "bg-white",
].join(" ")} firstOccur ? "text-[#ff5454] underline" : "text-black",
> ].join(" ")}
<a href={`https://stockplus.com/m/stocks/KOREA-A${row.Code}`}> >
{row.Name} <a href={`https://stockplus.com/m/stocks/KOREA-A${row.Code}`}>
</a> {row.Name}
</div> </a>
); </div>
})} );
</div> })}
</div>
);
} }
function StockList({ data }: { data: PageCorpsInfo }) { function StockList({ data }: { data: PageCorpsInfo }) {
@ -159,14 +120,13 @@ function StockList({ data }: { data: PageCorpsInfo }) {
const prevSet = i == 0 ? new Set<string>() : sets[i - 1]; const prevSet = i == 0 ? new Set<string>() : sets[i - 1];
const rows = corpListByDate[x]; const rows = corpListByDate[x];
return ( return (
<StockListByDate key={x} name={x} prevSet={prevSet} rows={rows}></StockListByDate> <StockListByDate key={x} name={x} prevSet={prevSet} rows={rows} />
); );
})} })}
</div> </div>
); );
} }
type FilterInfoOption = { type FilterInfoOption = {
list: { list: {
items: CorpSimple[]; items: CorpSimple[];
@ -193,17 +153,13 @@ function filterInfo(info: Coperation[], filterList: FilterInfoOption) {
} }
export default function StockListUI(props: StockProps) { export default function StockListUI(props: StockProps) {
const sig = useAsync<[PageCorpsInfo, CorpSimple[], CorpSimple[]]>(async () => { const sig = useAsync<[PageCorpsInfo, CorpSimple[], CorpSimple[]]>(() =>
const res = await Promise.all([ Promise.all([
fetch("/api/pages/" + encodeURIComponent(props.pageName)), fetchPageInfo(props.pageName),
fetch("/api/kospi"), fetchKospiList(),
fetch("/api/kosdaq"), fetchKosdaqList(),
]); ])
const corpsInfo = await res[0].json() as PageCorpsInfo; );
const kospi = await res[1].json();
const kosdaq = await res[2].json();
return [corpsInfo, kospi, kosdaq];
});
const viewKospi = useSignal(true); const viewKospi = useSignal(true);
const viewKosdaq = useSignal(false); const viewKosdaq = useSignal(false);
const viewOtherwise = useSignal(false); const viewOtherwise = useSignal(false);
@ -227,34 +183,47 @@ export default function StockListUI(props: StockProps) {
<p>File Loading Failed</p> <p>File Loading Failed</p>
</div> </div>
) )
: <StockList data={applyFilter(sig.value.data[0], sig.value.data[1], sig.value.data[2])}></StockList>} : (
<StockList
data={applyFilter(
sig.value.data[0],
sig.value.data[1],
sig.value.data[2],
)}
/>
)}
</div> </div>
)} )}
</div> </div>
</div> </div>
); );
function applyFilter(data: PageCorpsInfo, kospi: CorpSimple[], kosdaq: CorpSimple[]): PageCorpsInfo{ function applyFilter(
const filter = getFilters(kospi,kosdaq); data: PageCorpsInfo,
kospi: CorpSimple[],
kosdaq: CorpSimple[],
): PageCorpsInfo {
const filter = getFilters(kospi, kosdaq);
return { return {
name: data.name, name: data.name,
description: data.description, description: data.description,
corpListByDate: mapValues(data.corpListByDate, (it: Coperation[])=>{ corpListByDate: mapValues(data.corpListByDate, (it: Coperation[]) => {
return filterInfo(it, filter); return filterInfo(it, filter);
}) }),
} };
} }
function getFilters(kospi: CorpSimple[], kosdaq: CorpSimple[]): FilterInfoOption{ function getFilters(
kospi: CorpSimple[],
kosdaq: CorpSimple[],
): FilterInfoOption {
return { return {
otherwise: viewOtherwise.value, otherwise: viewOtherwise.value,
list: [{ list: [{
include: viewKospi.value, include: viewKospi.value,
items: kospi items: kospi,
}, }, {
{ include: viewKosdaq.value,
include: viewKosdaq.value, items: kosdaq,
items: kosdaq }],
} };
]
}
} }
} }

38
util/api.ts Normal file
View File

@ -0,0 +1,38 @@
export interface Coperation {
Name: string;
Code: string;
Sector: string;
Product: string;
ListingDay: string;
ClosingMonth: string;
Representative: string;
Homepage: string;
AddressArea: string;
LastUpdate: string;
}
export interface PageCorpsInfo {
name: string;
description: string;
corpListByDate: Record<string, Coperation[]>;
}
export interface CorpSimple {
code: string;
name: string;
}
export async function fetchPageInfo(pageName: string): Promise<PageCorpsInfo>{
const res = await fetch("/api/pages/" + encodeURIComponent(pageName));
return await res.json();
}
export async function fetchKospiList(): Promise<CorpSimple[]>{
const res = await fetch("/api/kospi");
return await res.json();
}
export async function fetchKosdaqList(): Promise<CorpSimple[]> {
const res = await fetch("/api/kosdaq");
return await res.json();
}

35
util/util.ts Normal file
View File

@ -0,0 +1,35 @@
import { Signal, useSignal } from "@preact/signals";
import { useEffect } from "preact/hooks";
export type QueryStatus<T> = {
type: "loading";
} | {
type: "complete";
data: T;
} | {
type: "error";
err: Error;
};
export function useAsync<T>(fn: () => Promise<T>): Signal<QueryStatus<T>> {
const state = useSignal({
type: "loading",
} as QueryStatus<T>);
useEffect(() => {
(async () => {
try {
const data = await fn();
state.value = {
type: "complete",
data: data,
};
} catch (err) {
state.value = {
type: "error",
err: err,
};
}
})();
}, []);
return state;
}