161 lines
6 KiB
TypeScript
161 lines
6 KiB
TypeScript
import { useComputed, type Signal } from "@preact/signals";
|
|
import { addplan, bplan, IPlan, Plan, storage_keys } from "../common/plan.ts";
|
|
import { datefmt, datefrom } from "../common/date.ts";
|
|
|
|
export interface CalendarProps {
|
|
date: Signal<number>;
|
|
plans: Signal<Plan[]>;
|
|
}
|
|
|
|
const casht_a = -800;
|
|
const casht_b = -950;
|
|
|
|
const calendar_span = 30;
|
|
|
|
const calendar_days = new Array(calendar_span).fill(0).map((_, i) => i);
|
|
|
|
export default function Calendar({ date, plans }: CalendarProps) {
|
|
const _calendar = useComputed(() => {
|
|
// cal
|
|
let e: number[] = [];
|
|
const cal: Record<number, IPlan[]> = {};
|
|
for (const p of plans.value) {
|
|
e = e.concat(addplan(cal, p));
|
|
}
|
|
e = Array.from(new Set(e)).sort((a, b) => a - b);
|
|
let c = 600;
|
|
let current = 600;
|
|
let alert = -1;
|
|
let die = -1;
|
|
/*
|
|
const cal_ex: Record<number, IPlan[]> = {};
|
|
const e_ex: number[] = [];
|
|
*/
|
|
// cash
|
|
for (const d of e) {
|
|
for (const p of cal[d]) {
|
|
c -= p.cost;
|
|
p.current = c;
|
|
if (alert === -1 && c < casht_a && d < datefrom(6, 1, 1))
|
|
alert = d;
|
|
if (die === -1 && c < casht_b && d < datefrom(6, 1, 1))
|
|
die = d;
|
|
/*
|
|
if (c < 0) {
|
|
e_ex.push(...(addplan(cal_ex, bplan({
|
|
name: "借款",
|
|
start: d,
|
|
price: -p.cost,
|
|
})).concat(addplan(cal_ex, bplan({
|
|
name: "还贷",
|
|
start: d + 360,
|
|
price: p.cost * 1.05,
|
|
})))));
|
|
}
|
|
*/
|
|
}
|
|
if (d < date.value + 30) {
|
|
current = c;
|
|
}
|
|
}
|
|
/*
|
|
for (const k in cal_ex) {
|
|
if (cal[k])
|
|
cal[k].push(...cal_ex[k]);
|
|
else
|
|
cal[k] = cal_ex[k];
|
|
}
|
|
e.push(...e_ex);
|
|
*/
|
|
return {
|
|
cal: cal,
|
|
e: e,
|
|
cash: current,
|
|
alert: alert,
|
|
die: die,
|
|
};
|
|
});
|
|
const calendar = useComputed(() => _calendar.value.cal);
|
|
const event_days = useComputed(() => _calendar.value.e);
|
|
const cash = useComputed(() => _calendar.value.cash);
|
|
const date_alert = useComputed(() => _calendar.value.alert);
|
|
const date_die = useComputed(() => _calendar.value.die);
|
|
const storage = useComputed(() => {
|
|
const st: Record<string, number> = {};
|
|
for (const d of event_days.value) {
|
|
if (d >= date.value + 30)
|
|
break;
|
|
for (const p of calendar.value[d]) {
|
|
if (!p.plan.role) continue;
|
|
for (let role of p.plan.role.split(",")) {
|
|
let amount = p.plan.amount;
|
|
// if (!role) continue;
|
|
if (role[0] === "-") {
|
|
amount = -amount;
|
|
role = role.slice(1);
|
|
}
|
|
if (st[role] === undefined)
|
|
st[role] = 0;
|
|
st[role] += amount;
|
|
}
|
|
}
|
|
}
|
|
return st;
|
|
});
|
|
let last_date = -1;
|
|
return <div class="calendar">
|
|
<div class="calendar__calendar">
|
|
<div class="calender__cash">¥{cash.value}</div>
|
|
<div class="calendar__head">
|
|
<button onClick={() => date.value -= 360}><<<</button>
|
|
<button onClick={() => date.value -= 90}><<</button>
|
|
<button onClick={() => date.value -= 30}><</button>
|
|
<span class="calendar__date">{datefmt(date.value)}</span>
|
|
<button onClick={() => date.value += 30}>></button>
|
|
<button onClick={() => date.value += 90}>>></button>
|
|
<button onClick={() => date.value += 360}>>>></button>
|
|
</div>
|
|
<div class="calendar__days">
|
|
{calendar_days.map(d => <span class={calendar.value[d + date.value] ? "p" : ""}>{d + 1}</span>)}
|
|
</div>
|
|
</div>
|
|
<div class="calendar__alert" hidden>
|
|
<p>{date_alert.value === -1 ? "" : "资金周转风险 (现金 < " + casht_a + "): " + datefmt(date_alert.value)}</p>
|
|
<p>{date_die.value === -1 ? "" : "资金链断裂 (现金 < " + casht_b + "): " + datefmt(date_die.value)}</p>
|
|
</div>
|
|
<table class="calendar__storage">
|
|
<thead>
|
|
<tr><td>库存</td><td>数量</td></tr>
|
|
</thead>
|
|
<tbody>
|
|
{Object.entries(storage.value).filter(st => storage_keys[st[0]] && st[1]).map(st => <tr><td>{storage_keys[st[0]]}</td><td>{st[1]}</td></tr>)}
|
|
</tbody>
|
|
</table>
|
|
<table class="calendar__events" cellSpacing="0">
|
|
<thead>
|
|
<tr>
|
|
<td>日期</td>
|
|
<td>描述</td>
|
|
<td>数量</td>
|
|
<td>次序</td>
|
|
<td>收支</td>
|
|
<td>余额</td>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{calendar_days.map(d => d + date.value).filter(d => calendar.value[d]).map(d => calendar.value[d].map(p => {
|
|
const e = <tr>
|
|
<td>{last_date === p.date ? "" : datefmt(p.date)}</td>
|
|
<td>{p.plan.name}</td>
|
|
<td>{p.plan.amount}</td>
|
|
<td>{p.plan.times === 1 ? "1" : "" + (p.order + 1) + " / " + p.plan.times}</td>
|
|
<td>{p.cost !== 0 ? "¥" + (-p.cost) : ""}</td>
|
|
<td>¥{p.current}</td>
|
|
</tr>
|
|
last_date = p.date;
|
|
return e;
|
|
}))}
|
|
</tbody>
|
|
</table>
|
|
</div>;
|
|
}
|