|
@@ -8,7 +8,8 @@ import {
|
|
|
AppstoreOutlined,
|
|
AppstoreOutlined,
|
|
|
ReloadOutlined,
|
|
ReloadOutlined,
|
|
|
TrophyOutlined,
|
|
TrophyOutlined,
|
|
|
- ShoppingOutlined
|
|
|
|
|
|
|
+ ShoppingOutlined,
|
|
|
|
|
+ CalendarOutlined
|
|
|
} from '@ant-design/icons'
|
|
} from '@ant-design/icons'
|
|
|
import dayjs from 'dayjs'
|
|
import dayjs from 'dayjs'
|
|
|
import { workRecordApi, personApi, itemApi } from '../services/api'
|
|
import { workRecordApi, personApi, itemApi } from '../services/api'
|
|
@@ -20,10 +21,13 @@ function Dashboard() {
|
|
|
const [loading, setLoading] = useState(false)
|
|
const [loading, setLoading] = useState(false)
|
|
|
const [countsLoading, setCountsLoading] = useState(false)
|
|
const [countsLoading, setCountsLoading] = useState(false)
|
|
|
const [monthlyLoading, setMonthlyLoading] = useState(false)
|
|
const [monthlyLoading, setMonthlyLoading] = useState(false)
|
|
|
|
|
+ const [yearlyLoading, setYearlyLoading] = useState(false)
|
|
|
const [selectedDate, setSelectedDate] = useState(dayjs())
|
|
const [selectedDate, setSelectedDate] = useState(dayjs())
|
|
|
const [selectedMonth, setSelectedMonth] = useState(dayjs())
|
|
const [selectedMonth, setSelectedMonth] = useState(dayjs())
|
|
|
|
|
+ const [selectedYear, setSelectedYear] = useState(dayjs())
|
|
|
const [summary, setSummary] = useState(null)
|
|
const [summary, setSummary] = useState(null)
|
|
|
const [monthlySummary, setMonthlySummary] = useState(null)
|
|
const [monthlySummary, setMonthlySummary] = useState(null)
|
|
|
|
|
+ const [yearlySummary, setYearlySummary] = useState(null)
|
|
|
const [personCount, setPersonCount] = useState(0)
|
|
const [personCount, setPersonCount] = useState(0)
|
|
|
const [itemCount, setItemCount] = useState(0)
|
|
const [itemCount, setItemCount] = useState(0)
|
|
|
const [error, setError] = useState(null)
|
|
const [error, setError] = useState(null)
|
|
@@ -71,6 +75,21 @@ function Dashboard() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Fetch yearly summary
|
|
|
|
|
+ const fetchYearlySummary = async (date) => {
|
|
|
|
|
+ setYearlyLoading(true)
|
|
|
|
|
+ try {
|
|
|
|
|
+ const year = date.year()
|
|
|
|
|
+ const response = await workRecordApi.getYearlySummary({ year })
|
|
|
|
|
+ setYearlySummary(response.data)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ message.error('获取年度统计失败: ' + error.message)
|
|
|
|
|
+ setYearlySummary(null)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setYearlyLoading(false)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Fetch counts for persons and items
|
|
// Fetch counts for persons and items
|
|
|
const fetchCounts = async () => {
|
|
const fetchCounts = async () => {
|
|
|
setCountsLoading(true)
|
|
setCountsLoading(true)
|
|
@@ -92,12 +111,14 @@ function Dashboard() {
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
fetchDailySummary(selectedDate)
|
|
fetchDailySummary(selectedDate)
|
|
|
fetchMonthlySummary(selectedMonth)
|
|
fetchMonthlySummary(selectedMonth)
|
|
|
|
|
+ fetchYearlySummary(selectedYear)
|
|
|
fetchCounts()
|
|
fetchCounts()
|
|
|
}, [])
|
|
}, [])
|
|
|
|
|
|
|
|
const handleRefresh = () => {
|
|
const handleRefresh = () => {
|
|
|
fetchDailySummary(selectedDate)
|
|
fetchDailySummary(selectedDate)
|
|
|
fetchMonthlySummary(selectedMonth)
|
|
fetchMonthlySummary(selectedMonth)
|
|
|
|
|
+ fetchYearlySummary(selectedYear)
|
|
|
fetchCounts()
|
|
fetchCounts()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -115,6 +136,13 @@ function Dashboard() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ const handleYearChange = (date) => {
|
|
|
|
|
+ if (date) {
|
|
|
|
|
+ setSelectedYear(date)
|
|
|
|
|
+ fetchYearlySummary(date)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const handleAddWorkRecord = () => {
|
|
const handleAddWorkRecord = () => {
|
|
|
navigate('/work-records')
|
|
navigate('/work-records')
|
|
|
}
|
|
}
|
|
@@ -184,6 +212,35 @@ function Dashboard() {
|
|
|
}
|
|
}
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
|
|
+ // Table columns for yearly summary
|
|
|
|
|
+ const monthNames = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
|
|
|
|
+ const yearlySummaryColumns = [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '人员',
|
|
|
|
|
+ dataIndex: 'person_name',
|
|
|
|
|
+ key: 'person_name',
|
|
|
|
|
+ fixed: 'left',
|
|
|
|
|
+ width: 100
|
|
|
|
|
+ },
|
|
|
|
|
+ ...monthNames.map((name, index) => ({
|
|
|
|
|
+ title: name,
|
|
|
|
|
+ dataIndex: ['monthly_earnings', index],
|
|
|
|
|
+ key: `month_${index}`,
|
|
|
|
|
+ align: 'right',
|
|
|
|
|
+ width: 80,
|
|
|
|
|
+ render: (value) => value ? `¥${value.toFixed(2)}` : '¥0.00'
|
|
|
|
|
+ })),
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '年度合计',
|
|
|
|
|
+ dataIndex: 'yearly_total',
|
|
|
|
|
+ key: 'yearly_total',
|
|
|
|
|
+ align: 'right',
|
|
|
|
|
+ fixed: 'right',
|
|
|
|
|
+ width: 100,
|
|
|
|
|
+ render: (value) => <strong>¥{(value || 0).toFixed(2)}</strong>
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
return (
|
|
return (
|
|
|
<div>
|
|
<div>
|
|
|
{error && (
|
|
{error && (
|
|
@@ -294,6 +351,57 @@ function Dashboard() {
|
|
|
</Spin>
|
|
</Spin>
|
|
|
</Card>
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
+ {/* Yearly Summary Section */}
|
|
|
|
|
+ <Card
|
|
|
|
|
+ title={
|
|
|
|
|
+ <Row justify="space-between" align="middle" gutter={[8, 8]}>
|
|
|
|
|
+ <Col xs={24} sm="auto">年度汇总</Col>
|
|
|
|
|
+ <Col xs={24} sm="auto" style={{ paddingBottom: 8 }}>
|
|
|
|
|
+ <DatePicker.YearPicker
|
|
|
|
|
+ value={selectedYear}
|
|
|
|
|
+ onChange={handleYearChange}
|
|
|
|
|
+ allowClear={false}
|
|
|
|
|
+ placeholder="选择年份"
|
|
|
|
|
+ style={{ width: isMobile ? '100%' : 'auto' }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Col>
|
|
|
|
|
+ </Row>
|
|
|
|
|
+ }
|
|
|
|
|
+ style={{ marginBottom: isMobile ? 12 : 24 }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Spin spinning={yearlyLoading}>
|
|
|
|
|
+ {yearlySummary?.persons?.length > 0 ? (
|
|
|
|
|
+ <Table
|
|
|
|
|
+ columns={yearlySummaryColumns}
|
|
|
|
|
+ dataSource={yearlySummary.persons}
|
|
|
|
|
+ rowKey="person_id"
|
|
|
|
|
+ pagination={false}
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ scroll={{ x: 1200 }}
|
|
|
|
|
+ summary={() => (
|
|
|
|
|
+ <Table.Summary fixed>
|
|
|
|
|
+ <Table.Summary.Row>
|
|
|
|
|
+ <Table.Summary.Cell index={0} fixed="left">
|
|
|
|
|
+ <strong>合计</strong>
|
|
|
|
|
+ </Table.Summary.Cell>
|
|
|
|
|
+ {(yearlySummary.monthly_totals || []).map((total, index) => (
|
|
|
|
|
+ <Table.Summary.Cell key={index} index={index + 1} align="right">
|
|
|
|
|
+ <strong>¥{(total || 0).toFixed(2)}</strong>
|
|
|
|
|
+ </Table.Summary.Cell>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ <Table.Summary.Cell index={13} align="right" fixed="right">
|
|
|
|
|
+ <strong>¥{(yearlySummary.grand_total || 0).toFixed(2)}</strong>
|
|
|
|
|
+ </Table.Summary.Cell>
|
|
|
|
|
+ </Table.Summary.Row>
|
|
|
|
|
+ </Table.Summary>
|
|
|
|
|
+ )}
|
|
|
|
|
+ />
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <Empty description="该年度暂无工作记录" />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Spin>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
<div style={{ marginBottom: isMobile ? 12 : 24, display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8 }}>
|
|
<div style={{ marginBottom: isMobile ? 12 : 24, display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8 }}>
|
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
|
<DatePicker
|
|
<DatePicker
|