|
|
// frontend/components/automation/AutomationClient.tsx — 자동화 페이지 (auto.jsx 이식)
|
|
|
"use client";
|
|
|
import { useState } from "react";
|
|
|
import { Icon } from "@/components/Icon";
|
|
|
import { SubRail } from "@/components/SubRail";
|
|
|
import type { RailItem } from "@/components/SubRail";
|
|
|
import { useAutomation } from "@/lib/hooks/useAutomation";
|
|
|
import type { ParsePreview, SuggestionOut } from "@/lib/types";
|
|
|
import { LogView } from "./LogView";
|
|
|
import { NewView } from "./NewView";
|
|
|
import { RulesView } from "./RulesView";
|
|
|
import { SuggestView } from "./SuggestView";
|
|
|
|
|
|
const AU_HEAD: Record<string, { title: string; em: string }> = {
|
|
|
rules: { title: "내 규칙", em: "켜두면 알아서 굴러가요" },
|
|
|
new: { title: "새 자동화", em: "말하듯 적으면 규칙이 돼요" },
|
|
|
suggest: { title: "아리 제안", em: "반복되는 일을 자동으로" },
|
|
|
log: { title: "실행 기록", em: "규칙이 움직인 순간들" },
|
|
|
};
|
|
|
|
|
|
const AU_RAIL = (sugN: number): RailItem[] => [
|
|
|
{ id: "rules", icon: "zap", label: "내 규칙" },
|
|
|
{ id: "new", icon: "plus", label: "새 자동화" },
|
|
|
{ sep: true },
|
|
|
{ id: "suggest", icon: "brain", label: "아리 제안", dot: sugN > 0 },
|
|
|
{ id: "log", icon: "clock", label: "실행 기록" },
|
|
|
];
|
|
|
|
|
|
export function AutomationClient() {
|
|
|
const [view, setView] = useState("rules");
|
|
|
const [toast, setToast] = useState<string | null>(null);
|
|
|
const { page, error, parse, createRule, toggle, accept, dismiss, reload } = useAutomation();
|
|
|
|
|
|
if (error) {
|
|
|
return (
|
|
|
<div className="dash-error" role="alert">
|
|
|
<p>자동화를 불러오지 못했어요.</p>
|
|
|
<button className="mini-cta" onClick={() => reload()}>
|
|
|
<Icon name="swap" /> 다시 시도
|
|
|
</button>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
if (!page) {
|
|
|
return (
|
|
|
<div className="auto-page">
|
|
|
<div className="pagehead">
|
|
|
<div>
|
|
|
<h1 className="ph-title">자동화</h1>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const activeN = page.rules.filter((r) => r.on).length;
|
|
|
const h = AU_HEAD[view];
|
|
|
|
|
|
const fire = (name: string) => {
|
|
|
setToast(`‘${name}’ 규칙을 켰어요.`);
|
|
|
setView("rules");
|
|
|
setTimeout(() => setToast(null), 3200);
|
|
|
};
|
|
|
const createFromPreview = async (p: ParsePreview) => {
|
|
|
await createRule({
|
|
|
name: p.name,
|
|
|
cat: p.cat,
|
|
|
trigger: p.parse.trigger,
|
|
|
cond: p.parse.cond,
|
|
|
action: p.parse.action,
|
|
|
});
|
|
|
fire(p.name);
|
|
|
};
|
|
|
const acceptSug = async (s: SuggestionOut) => {
|
|
|
await accept(s.id);
|
|
|
fire(s.offer_name);
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
<div className="auto-page" data-screen-label="자동화">
|
|
|
<div className="pagehead">
|
|
|
<div>
|
|
|
<div className="ph-eyebrow">
|
|
|
<span>활성 규칙 {activeN}개</span>
|
|
|
<span className="sep" />
|
|
|
<span>이번 주 {page.stats.runs_week}회 실행</span>
|
|
|
<span className="sep" />
|
|
|
<span>{page.stats.saved} 아꼈어요</span>
|
|
|
</div>
|
|
|
<h1 className="ph-title">
|
|
|
{h.title} <em>{h.em}</em>
|
|
|
</h1>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div className="work">
|
|
|
<SubRail items={AU_RAIL(page.suggests.length)} active={view} onPick={setView} />
|
|
|
{view === "rules" && <RulesView rules={page.rules} toggle={toggle} />}
|
|
|
{view === "new" && <NewView parse={parse} onCreate={createFromPreview} />}
|
|
|
{view === "suggest" && (
|
|
|
<SuggestView
|
|
|
suggests={page.suggests}
|
|
|
accept={acceptSug}
|
|
|
dismiss={(s) => dismiss(s.id)}
|
|
|
/>
|
|
|
)}
|
|
|
{view === "log" && <LogView log={page.log} />}
|
|
|
</div>
|
|
|
{toast && (
|
|
|
<div className="au-toast">
|
|
|
<Icon name="tick" />
|
|
|
{toast}
|
|
|
</div>
|
|
|
)}
|
|
|
</div>
|
|
|
);
|
|
|
}
|