feat🔫: 单个cls的label pie
This commit is contained in:
parent
d27e2d9aae
commit
c4e66e2e52
|
@ -1,22 +1,21 @@
|
|||
.home {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.total {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.monthBar {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: right;
|
||||
}
|
||||
}
|
||||
.home {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.total {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.monthBar {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid: repeat(2, auto) / auto-flow auto;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,123 +1,137 @@
|
|||
import styles from "./Home.module.scss"
|
||||
import * as R from 'ramda'
|
||||
import { BillType, IBill } from "../../model"
|
||||
import Bar from "../../components/charts/bar"
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { BillContext } from "../../store";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Pie from "../../components/charts/pie";
|
||||
import {
|
||||
Card,
|
||||
Modal,
|
||||
DatePicker,
|
||||
Radio,
|
||||
Space,
|
||||
} from "antd";
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/zh-cn';
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const Home = () => {
|
||||
const billStore = useContext(BillContext)
|
||||
|
||||
const transformer = (record: Record<string, IBill[]>) => {
|
||||
const funcs = R.compose(
|
||||
R.sort((a: { x: string, y: number }, b) => {
|
||||
const date1 = dayjs(a.x).toDate().getTime()
|
||||
const date2 = dayjs(b.x).toDate().getTime()
|
||||
|
||||
return date1 - date2
|
||||
}),
|
||||
R.map((key: string) => {
|
||||
const moneys = record[key].map(bill => bill.money)
|
||||
return {
|
||||
x: key,
|
||||
y: Number(R.sum(moneys).toFixed(2)),
|
||||
}
|
||||
}))
|
||||
return funcs(R.keys(record))
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const [year, setYear] = useState(now.getFullYear())
|
||||
const [month, setMonth] = useState(now.getMonth() + 1)
|
||||
|
||||
useEffect(() => {
|
||||
billStore.fetch(year, month).then()
|
||||
}, [year, month])
|
||||
|
||||
const changeDate = (date: moment.Moment | null, datestring: string) => {
|
||||
const d = date?.toDate() ?? new Date()
|
||||
setYear(d.getFullYear())
|
||||
setMonth(d.getMonth() + 1)
|
||||
}
|
||||
|
||||
const typeOpt = [
|
||||
{ label: '支出', value: BillType.consume },
|
||||
{ label: '收入', value: BillType.income },
|
||||
];
|
||||
const [billType, setBillType] = useState(BillType.consume)
|
||||
|
||||
// 点击bar弹出当天pie
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [modalTitle, setModalTitle] = useState("");
|
||||
const [modalData, setModalData] = useState<{
|
||||
x: string
|
||||
y: number
|
||||
}[]>([]);
|
||||
|
||||
return (
|
||||
<div className={styles.home}>
|
||||
<div className={styles.total}>
|
||||
<Space align="start">
|
||||
<DatePicker
|
||||
picker="month"
|
||||
value={moment(`${year}-${month}`, 'YYYY-MM')}
|
||||
onChange={changeDate}
|
||||
/>
|
||||
<Radio.Group
|
||||
options={typeOpt}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
value={billType}
|
||||
onChange={e => setBillType(e.target.value)}
|
||||
/>
|
||||
<Card>
|
||||
{"总金额"}
|
||||
¥{billStore.getTotalMoney(billType)}
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
<div className={styles.monthBar}>
|
||||
<Bar
|
||||
data={transformer(billStore.groupByDate(billType))}
|
||||
onClickItem={date => {
|
||||
setIsModalOpen(true)
|
||||
setModalTitle(date)
|
||||
setModalData(transformer(billStore.groupByClass(billType, date)))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.cards}>
|
||||
<Card >
|
||||
<Pie
|
||||
title="本月消费分类"
|
||||
data={transformer(billStore.groupByClass(billType))}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
<Modal
|
||||
visible={isModalOpen}
|
||||
onOk={() => setIsModalOpen(false)}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
title={modalTitle}
|
||||
>
|
||||
<Pie
|
||||
data={modalData}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import styles from "./Home.module.scss"
|
||||
import * as R from 'ramda'
|
||||
import { BillType, IBill } from "../../model"
|
||||
import Bar from "../../components/charts/bar"
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { BillContext } from "../../store";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Pie from "../../components/charts/pie";
|
||||
import {
|
||||
Card,
|
||||
Modal,
|
||||
DatePicker,
|
||||
Radio,
|
||||
Space,
|
||||
} from "antd";
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/zh-cn';
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const Home = () => {
|
||||
const billStore = useContext(BillContext)
|
||||
|
||||
const transformer = (record: Record<string, IBill[]>) => {
|
||||
const funcs = R.compose(
|
||||
R.sort((a: { x: string, y: number }, b) => {
|
||||
const date1 = dayjs(a.x).toDate().getTime()
|
||||
const date2 = dayjs(b.x).toDate().getTime()
|
||||
|
||||
return date1 - date2
|
||||
}),
|
||||
R.map((key: string) => {
|
||||
const moneys = record[key].map(bill => bill.money)
|
||||
return {
|
||||
x: key,
|
||||
y: Number(R.sum(moneys).toFixed(2)),
|
||||
}
|
||||
}))
|
||||
return funcs(R.keys(record))
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const [year, setYear] = useState(now.getFullYear())
|
||||
const [month, setMonth] = useState(now.getMonth() + 1)
|
||||
|
||||
useEffect(() => {
|
||||
billStore.fetch(year, month).then()
|
||||
}, [year, month])
|
||||
|
||||
const changeDate = (date: moment.Moment | null, datestring: string) => {
|
||||
const d = date?.toDate() ?? new Date()
|
||||
setYear(d.getFullYear())
|
||||
setMonth(d.getMonth() + 1)
|
||||
}
|
||||
|
||||
const typeOpt = [
|
||||
{ label: '支出', value: BillType.consume },
|
||||
{ label: '收入', value: BillType.income },
|
||||
];
|
||||
const [billType, setBillType] = useState(BillType.consume)
|
||||
|
||||
// 点击bar弹出当天pie
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [modalTitle, setModalTitle] = useState("");
|
||||
const [modalData, setModalData] = useState<{
|
||||
x: string
|
||||
y: number
|
||||
}[]>([]);
|
||||
|
||||
// 显示单个cls的饼状图,查看cls内部的label的消费情况,
|
||||
// 这里有一个cls列表
|
||||
const clsesForShow = ["餐饮", "恋爱"]
|
||||
|
||||
return (
|
||||
<div className={styles.home}>
|
||||
<div className={styles.total}>
|
||||
<Space align="start">
|
||||
<DatePicker
|
||||
picker="month"
|
||||
value={moment(`${year}-${month}`, 'YYYY-MM')}
|
||||
onChange={changeDate}
|
||||
/>
|
||||
<Radio.Group
|
||||
options={typeOpt}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
value={billType}
|
||||
onChange={e => setBillType(e.target.value)}
|
||||
/>
|
||||
<Card>
|
||||
{"总金额"}
|
||||
¥{billStore.getTotalMoney(billType)}
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
<div className={styles.monthBar}>
|
||||
<Bar
|
||||
data={transformer(billStore.groupByDate(billType))}
|
||||
onClickItem={date => {
|
||||
setIsModalOpen(true)
|
||||
setModalTitle(date)
|
||||
setModalData(transformer(billStore.groupByClass(billType, date)))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.cards}>
|
||||
<Card >
|
||||
<Pie
|
||||
title="本月消费分类"
|
||||
data={transformer(billStore.groupByClass(billType))}
|
||||
/>
|
||||
</Card>
|
||||
{
|
||||
clsesForShow.map(cls => {
|
||||
return (<Card>
|
||||
<Pie
|
||||
title={cls}
|
||||
data={transformer(billStore.groupByLabelOfClass(cls))}
|
||||
/>
|
||||
</Card>)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<Modal
|
||||
visible={isModalOpen}
|
||||
onOk={() => setIsModalOpen(false)}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
title={modalTitle}
|
||||
>
|
||||
<Pie
|
||||
data={modalData}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(Home)
|
|
@ -1,123 +1,133 @@
|
|||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
import { createContext } from "react";
|
||||
import { createBill, getBills, getClass } from "../api/bills";
|
||||
import { BillType, IBill } from "../model";
|
||||
import * as R from "ramda"
|
||||
|
||||
/**
|
||||
* 仅存储一个月的数据
|
||||
*/
|
||||
export class Bill {
|
||||
private _bills: IBill[] = [];
|
||||
// _cls2label: IClass = {consume: new Map<string, string[]>(), income: []}
|
||||
private _cls2label: { consume: Record<string, string[]>, income: [] } = { consume: {}, income: [] }
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this)
|
||||
this.fetchClass().then()
|
||||
}
|
||||
|
||||
get bills() {
|
||||
return this._bills
|
||||
}
|
||||
|
||||
get cls2label() {
|
||||
return this._cls2label
|
||||
}
|
||||
|
||||
groupByDate(type?: BillType) {
|
||||
const classFun = R.filter((bill: IBill) => R.of(bill.type).length === 0 || bill.type === type)
|
||||
const functions = R.compose(
|
||||
R.groupBy((bill: IBill) => bill.date),
|
||||
classFun,
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
groupByClass(type?: BillType, date?: string) {
|
||||
const classFun = R.filter((bill: IBill) => R.of(bill.type).length === 0 || bill.type === type && (date ? bill.date === date : true))
|
||||
const functions = R.compose(
|
||||
R.groupBy((bill: IBill) => bill.cls),
|
||||
classFun,
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
get listDailyMoney() {
|
||||
return this.groupByDate
|
||||
}
|
||||
|
||||
get totalMoney() {
|
||||
const functions = R.compose(
|
||||
Number,
|
||||
(s: number) => s.toFixed(2),
|
||||
R.sum,
|
||||
R.map((bill: IBill) => bill.money)
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
get consumeMoney() {
|
||||
const functions = R.compose(
|
||||
Number,
|
||||
(s: number) => s.toFixed(2),
|
||||
R.sum,
|
||||
R.map((bill: IBill) => bill.money),
|
||||
R.filter((bill: IBill) => bill.type === BillType.consume),
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
get incomeMoney() {
|
||||
const functions = R.compose(
|
||||
Number,
|
||||
(s: number) => s.toFixed(2),
|
||||
R.sum,
|
||||
R.map((bill: IBill) => bill.money),
|
||||
R.filter((bill: IBill) => bill.type === BillType.income),
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
getTotalMoney(type?: BillType) {
|
||||
switch (type) {
|
||||
case BillType.income:
|
||||
return this.incomeMoney
|
||||
case BillType.consume:
|
||||
return this.consumeMoney
|
||||
default:
|
||||
return this.totalMoney
|
||||
}
|
||||
}
|
||||
|
||||
get meanMoneyByDate() {
|
||||
const days = Reflect.ownKeys(this.groupByDate).length
|
||||
if (days === 0) return 0
|
||||
return this.totalMoney / days
|
||||
}
|
||||
|
||||
|
||||
async add(bill: IBill) {
|
||||
const { id } = await createBill(bill)
|
||||
bill.id = id
|
||||
runInAction(() => {
|
||||
this._bills.push(bill);
|
||||
})
|
||||
}
|
||||
|
||||
async fetch(year: number, month: number) {
|
||||
const data = await getBills(year, month)
|
||||
runInAction(() => {
|
||||
this._bills = data
|
||||
})
|
||||
}
|
||||
|
||||
async fetchClass() {
|
||||
const cls2label = await getClass()
|
||||
runInAction(() => {
|
||||
this._cls2label = cls2label
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const BillContext = createContext<Bill>(new Bill());
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
import { createContext } from "react";
|
||||
import { createBill, getBills, getClass } from "../api/bills";
|
||||
import { BillType, IBill } from "../model";
|
||||
import * as R from "ramda"
|
||||
|
||||
/**
|
||||
* 仅存储一个月的数据
|
||||
*/
|
||||
export class Bill {
|
||||
private _bills: IBill[] = [];
|
||||
// _cls2label: IClass = {consume: new Map<string, string[]>(), income: []}
|
||||
private _cls2label: { consume: Record<string, string[]>, income: [] } = { consume: {}, income: [] }
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this)
|
||||
this.fetchClass().then()
|
||||
}
|
||||
|
||||
get bills() {
|
||||
return this._bills
|
||||
}
|
||||
|
||||
get cls2label() {
|
||||
return this._cls2label
|
||||
}
|
||||
|
||||
groupByDate(type?: BillType) {
|
||||
const classFun = R.filter((bill: IBill) => R.of(bill.type).length === 0 || bill.type === type)
|
||||
const functions = R.compose(
|
||||
R.groupBy((bill: IBill) => bill.date),
|
||||
classFun,
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
groupByClass(type?: BillType, date?: string) {
|
||||
const classFun = R.filter((bill: IBill) => R.of(bill.type).length === 0 || bill.type === type && (date ? bill.date === date : true))
|
||||
const functions = R.compose(
|
||||
R.groupBy((bill: IBill) => bill.cls),
|
||||
classFun,
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
groupByLabelOfClass(className: string) {
|
||||
const classFun = R.filter((bill: IBill) => R.of(bill.type).length === 0 || bill.cls === className)
|
||||
const functions = R.compose(
|
||||
R.groupBy((bill: IBill) => bill.label),
|
||||
classFun,
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
|
||||
get listDailyMoney() {
|
||||
return this.groupByDate
|
||||
}
|
||||
|
||||
get totalMoney() {
|
||||
const functions = R.compose(
|
||||
Number,
|
||||
(s: number) => s.toFixed(2),
|
||||
R.sum,
|
||||
R.map((bill: IBill) => bill.money)
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
get consumeMoney() {
|
||||
const functions = R.compose(
|
||||
Number,
|
||||
(s: number) => s.toFixed(2),
|
||||
R.sum,
|
||||
R.map((bill: IBill) => bill.money),
|
||||
R.filter((bill: IBill) => bill.type === BillType.consume),
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
get incomeMoney() {
|
||||
const functions = R.compose(
|
||||
Number,
|
||||
(s: number) => s.toFixed(2),
|
||||
R.sum,
|
||||
R.map((bill: IBill) => bill.money),
|
||||
R.filter((bill: IBill) => bill.type === BillType.income),
|
||||
)
|
||||
return functions(this._bills)
|
||||
}
|
||||
|
||||
getTotalMoney(type?: BillType) {
|
||||
switch (type) {
|
||||
case BillType.income:
|
||||
return this.incomeMoney
|
||||
case BillType.consume:
|
||||
return this.consumeMoney
|
||||
default:
|
||||
return this.totalMoney
|
||||
}
|
||||
}
|
||||
|
||||
get meanMoneyByDate() {
|
||||
const days = Reflect.ownKeys(this.groupByDate).length
|
||||
if (days === 0) return 0
|
||||
return this.totalMoney / days
|
||||
}
|
||||
|
||||
|
||||
async add(bill: IBill) {
|
||||
const { id } = await createBill(bill)
|
||||
bill.id = id
|
||||
runInAction(() => {
|
||||
this._bills.push(bill);
|
||||
})
|
||||
}
|
||||
|
||||
async fetch(year: number, month: number) {
|
||||
const data = await getBills(year, month)
|
||||
runInAction(() => {
|
||||
this._bills = data
|
||||
})
|
||||
}
|
||||
|
||||
async fetchClass() {
|
||||
const cls2label = await getClass()
|
||||
runInAction(() => {
|
||||
this._cls2label = cls2label
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const BillContext = createContext<Bill>(new Bill());
|
||||
|
|
Loading…
Reference in New Issue
Block a user