feat🔫: 单个cls的label pie

This commit is contained in:
车厘子 2022-10-18 17:13:04 +08:00
parent d27e2d9aae
commit c4e66e2e52
3 changed files with 290 additions and 267 deletions

View File

@ -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;
}
}

View File

@ -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)

View File

@ -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());