レポート通知システム
:LiTarget: 用途
日次/週次レポートを自動生成してSlack/メール送信するパターン。
:LiSparkle: 特徴
- スケジュール実行
- Slack送信
- メール送信
- PDF添付
:LiCode: 実コード(SCALE Base より自動抽出)
:LiInfo:
lib/report-notify.tsの中身そのもの。コピペ即可。
// 日報・作業報告のSlack自動通知ヘルパー
// チャンネル: #作業報告-日報提出_ligl
// 仕様: 1日1スレッドに集約 — 親メッセージ「◯/◯ 日次報告(ユーザー名)」を作り、
// 朝日報・作業開始・作業終了・夜日報をすべてその親メッセージのスレッド返信として投稿する。
export const REPORT_CHANNEL = 'C08MKSMTPNZ';
const threadKey = (userId: string, date: string) => `scale-slack-daily-thread:${userId}:${date}`;
// その日の親メッセージts を取得(無ければ作成して localStorage に保存)
export async function ensureDailyThread(userId: string, userName: string, date: string): Promise<string | null> {
if (typeof window === 'undefined') return null;
const k = threadKey(userId, date);
const cached = localStorage.getItem(k);
if (cached) return cached;
// YYYY-MM-DD → M/D
const m = date.match(/^\d{4}-(\d{2})-(\d{2})$/);
const mmdd = m ? `${parseInt(m[1], 10)}/${parseInt(m[2], 10)}` : date;
const title = `:date: *${mmdd} 日次報告(${userName})*`;
try {
const res = await fetch('/api/slack-post', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel: REPORT_CHANNEL, text: title }),
});
const data = await res.json() as { ok?: boolean; ts?: string };
if (data.ok && data.ts) {
try { localStorage.setItem(k, data.ts); } catch {}
return data.ts;
}
} catch {}
return null;
}
export interface PostReportOptions {
userId?: string;
userName?: string;
date?: string; // YYYY-MM-DD(その日の親スレッドに紐づける)
}
export async function postReport(text: string, opts?: PostReportOptions): Promise<boolean> {
try {
let thread_ts: string | null = null;
if (opts?.userId && opts?.userName && opts?.date) {
thread_ts = await ensureDailyThread(opts.userId, opts.userName, opts.date);
}
const res = await fetch('/api/slack-post', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel: REPORT_CHANNEL, text, thread_ts: thread_ts || undefined }),
});
const data = await res.json();
return !!data.ok;
} catch {
return false;
}
}
export function fmtHoursMin(minutes: number): string {
const h = Math.floor(minutes / 60);
const m = minutes % 60;
if (h === 0) return `${m}分`;
if (m === 0) return `${h}時間`;
return `${h}時間${m}分`;
}
:LiFolder: ソースファイルのパス
/Users/oogushiyuuki/Library/CloudStorage/GoogleDrive-y-ogushi@scale-group.co.jp/マイドライブ/AI/scale-base/lib/report-notify.ts
:LiHandPointer: 使い方
対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。
:LiAlertCircle: 注意事項
- 依存パッケージを忘れず追加