@beppla/tapas-ui 1.2.33 → 1.2.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,337 @@
1
+ import React, { useMemo } from 'react';
2
+ import { View, ScrollView, StyleSheet, Platform } from 'react-native';
3
+ import Text from '../Text/Text';
4
+ import Pagination from '../Pagination/Pagination';
5
+
6
+ export interface StatisticsTableColumn {
7
+ key: string;
8
+ title: string;
9
+ width?: number;
10
+ }
11
+
12
+ export interface StatisticsTableCell {
13
+ rowKey: string;
14
+ columnKey: string;
15
+ quantity: number;
16
+ amount: number;
17
+ }
18
+
19
+ export interface StatisticsTableProps {
20
+ // 行标题(如日期)
21
+ rows: Array<{ key: string; label: string }>;
22
+ // 列标题(如门店)
23
+ columns: StatisticsTableColumn[];
24
+ // 单元格数据
25
+ cells: StatisticsTableCell[];
26
+ // 是否显示行统计(Sum/Mean 列)
27
+ showRowStats?: boolean;
28
+ // 是否显示列统计(Sum/Mean 行)
29
+ showColumnStats?: boolean;
30
+ // 分页配置
31
+ pagination?: {
32
+ current: number;
33
+ pageSize: number;
34
+ total: number;
35
+ onChange?: (page: number, pageSize: number) => void;
36
+ } | false;
37
+ loading?: boolean;
38
+ emptyText?: string;
39
+ maxHeight?: number;
40
+ rowLabelWidth?: number;
41
+ style?: any;
42
+ }
43
+
44
+ export function StatisticsTable({
45
+ rows,
46
+ columns,
47
+ cells,
48
+ showRowStats = false,
49
+ showColumnStats = false,
50
+ pagination,
51
+ loading = false,
52
+ emptyText = 'No data',
53
+ maxHeight = 500,
54
+ rowLabelWidth = 150,
55
+ style,
56
+ }: StatisticsTableProps) {
57
+
58
+ // 构建矩阵数据
59
+ const matrix = useMemo(() => {
60
+ const data: Record<string, Record<string, { quantity: number; amount: number }>> = {};
61
+ rows.forEach(row => {
62
+ if (!data[row.key]) {
63
+ data[row.key] = {};
64
+ }
65
+ columns.forEach(col => {
66
+ const cell = cells.find(c => c.rowKey === row.key && c.columnKey === col.key);
67
+ data[row.key]![col.key] = cell || { quantity: 0, amount: 0 };
68
+ });
69
+ });
70
+ return data;
71
+ }, [rows, columns, cells]);
72
+
73
+ // 计算行统计(每行的总和和平均值)
74
+ const rowStats = useMemo(() => {
75
+ if (!showRowStats) return null;
76
+ return rows.map(row => {
77
+ const rowCells = columns.map(col => matrix[row.key]?.[col.key] || { quantity: 0, amount: 0 });
78
+ const sumQuantity = rowCells.reduce((sum, cell) => sum + cell.quantity, 0);
79
+ const sumAmount = rowCells.reduce((sum, cell) => sum + cell.amount, 0);
80
+ const count = rowCells.length || 1;
81
+ const meanQuantity = sumQuantity / count;
82
+ const meanAmount = sumAmount / count;
83
+ return { sumQuantity, sumAmount, meanQuantity, meanAmount };
84
+ });
85
+ }, [rows, columns, matrix, showRowStats]);
86
+
87
+ // 计算列统计(每列的总和和平均值)
88
+ const columnStats = useMemo(() => {
89
+ if (!showColumnStats) return null;
90
+ return columns.map(col => {
91
+ const colCells = rows.map(row => matrix[row.key]?.[col.key] || { quantity: 0, amount: 0 });
92
+ const sumQuantity = colCells.reduce((sum, cell) => sum + cell.quantity, 0);
93
+ const sumAmount = colCells.reduce((sum, cell) => sum + cell.amount, 0);
94
+ const count = colCells.length || 1;
95
+ const meanQuantity = sumQuantity / count;
96
+ const meanAmount = sumAmount / count;
97
+ return { sumQuantity, sumAmount, meanQuantity, meanAmount };
98
+ });
99
+ }, [rows, columns, matrix, showColumnStats]);
100
+
101
+ if (loading) {
102
+ return (
103
+ <View style={[styles.container, style]}>
104
+ <View style={styles.loadingRow}>
105
+ <Text>Loading...</Text>
106
+ </View>
107
+ </View>
108
+ );
109
+ }
110
+
111
+ if (rows.length === 0 || columns.length === 0) {
112
+ return (
113
+ <View style={[styles.container, style]}>
114
+ <View style={styles.emptyRow}>
115
+ <Text style={styles.emptyText}>{emptyText}</Text>
116
+ </View>
117
+ </View>
118
+ );
119
+ }
120
+
121
+ return (
122
+ <View style={[styles.container, style]}>
123
+ <ScrollView
124
+ horizontal
125
+ showsHorizontalScrollIndicator={Platform.OS === 'web'}
126
+ >
127
+ <View>
128
+ {/* Table Header */}
129
+ <View style={styles.header}>
130
+ <View style={[styles.headerCell, { width: rowLabelWidth }]}>
131
+ <Text style={styles.headerText}></Text>
132
+ </View>
133
+ {columns.map((column) => (
134
+ <View key={column.key} style={[styles.headerCell, { width: column.width || 150 }]}>
135
+ <Text style={styles.headerText}>{column.title}</Text>
136
+ </View>
137
+ ))}
138
+ {showRowStats && (
139
+ <>
140
+ <View style={[styles.headerCell, { width: 120 }]}>
141
+ <Text style={styles.headerText}>Sum</Text>
142
+ </View>
143
+ <View style={[styles.headerCell, { width: 120 }]}>
144
+ <Text style={styles.headerText}>Mean</Text>
145
+ </View>
146
+ </>
147
+ )}
148
+ </View>
149
+
150
+ {/* Table Body */}
151
+ <ScrollView
152
+ style={{ maxHeight }}
153
+ showsVerticalScrollIndicator={Platform.OS === 'web'}
154
+ >
155
+ {rows.map((row, rowIndex) => {
156
+ const rowStat = rowStats?.[rowIndex];
157
+
158
+ return (
159
+ <View key={row.key} style={styles.row}>
160
+ <View style={[styles.cell, { width: rowLabelWidth }]}>
161
+ <Text style={styles.rowLabel}>{row.label}</Text>
162
+ </View>
163
+ {columns.map((column) => {
164
+ const cell = matrix[row.key]?.[column.key] || { quantity: 0, amount: 0 };
165
+ return (
166
+ <View key={column.key} style={[styles.cell, { width: column.width || 150 }]}>
167
+ <Text style={styles.cellQuantity}>{cell.quantity}</Text>
168
+ <Text style={styles.cellAmount}>€{cell.amount.toFixed(2)}</Text>
169
+ </View>
170
+ );
171
+ })}
172
+ {showRowStats && rowStat && (
173
+ <>
174
+ <View style={[styles.cell, { width: 120 }]}>
175
+ <Text style={styles.cellQuantity}>{rowStat.sumQuantity}</Text>
176
+ <Text style={styles.cellAmount}>€{rowStat.sumAmount.toFixed(2)}</Text>
177
+ </View>
178
+ <View style={[styles.cell, { width: 120 }]}>
179
+ <Text style={styles.cellQuantity}>{rowStat.meanQuantity.toFixed(1)}</Text>
180
+ <Text style={styles.cellAmount}>€{rowStat.meanAmount.toFixed(2)}</Text>
181
+ </View>
182
+ </>
183
+ )}
184
+ </View>
185
+ );
186
+ })}
187
+
188
+ {/* Column Statistics (Sum/Mean rows) */}
189
+ {showColumnStats && columnStats && (
190
+ <>
191
+ <View style={[styles.row, styles.statsRow]}>
192
+ <View style={[styles.cell, { width: rowLabelWidth }]}>
193
+ <Text style={styles.statsLabel}>Sum</Text>
194
+ </View>
195
+ {columns.map((column, colIndex) => {
196
+ const stat = columnStats[colIndex];
197
+ return (
198
+ <View key={column.key} style={[styles.cell, { width: column.width || 150 }]}>
199
+ <Text style={styles.cellQuantity}>{stat?.sumQuantity || 0}</Text>
200
+ <Text style={styles.cellAmount}>€{stat?.sumAmount.toFixed(2) || '0.00'}</Text>
201
+ </View>
202
+ );
203
+ })}
204
+ {showRowStats && (
205
+ <>
206
+ <View style={[styles.cell, { width: 120 }]} />
207
+ <View style={[styles.cell, { width: 120 }]} />
208
+ </>
209
+ )}
210
+ </View>
211
+ <View style={[styles.row, styles.statsRow]}>
212
+ <View style={[styles.cell, { width: rowLabelWidth }]}>
213
+ <Text style={styles.statsLabel}>Mean</Text>
214
+ </View>
215
+ {columns.map((column, colIndex) => {
216
+ const stat = columnStats[colIndex];
217
+ return (
218
+ <View key={column.key} style={[styles.cell, { width: column.width || 150 }]}>
219
+ <Text style={styles.cellQuantity}>{stat?.meanQuantity.toFixed(1) || '0.0'}</Text>
220
+ <Text style={styles.cellAmount}>€{stat?.meanAmount.toFixed(2) || '0.00'}</Text>
221
+ </View>
222
+ );
223
+ })}
224
+ {showRowStats && (
225
+ <>
226
+ <View style={[styles.cell, { width: 120 }]} />
227
+ <View style={[styles.cell, { width: 120 }]} />
228
+ </>
229
+ )}
230
+ </View>
231
+ </>
232
+ )}
233
+ </ScrollView>
234
+ </View>
235
+ </ScrollView>
236
+
237
+ {/* Pagination */}
238
+ {pagination !== false && pagination && (
239
+ <View style={styles.paginationContainer}>
240
+ <Pagination
241
+ totalCount={`${pagination.total} items`}
242
+ pageParams={{
243
+ page: pagination.current,
244
+ pageSize: pagination.pageSize,
245
+ totalItems: pagination.total,
246
+ totalPage: Math.ceil(pagination.total / pagination.pageSize),
247
+ }}
248
+ onPageChange={(params) => {
249
+ pagination.onChange?.(params.page, params.pageSize);
250
+ }}
251
+ />
252
+ </View>
253
+ )}
254
+ </View>
255
+ );
256
+ }
257
+
258
+ const styles = StyleSheet.create({
259
+ container: {
260
+ backgroundColor: '#FFFFFF',
261
+ borderRadius: 8,
262
+ overflow: 'hidden',
263
+ },
264
+ header: {
265
+ flexDirection: 'row',
266
+ backgroundColor: '#F5F5F5',
267
+ borderBottomWidth: 2,
268
+ borderBottomColor: '#E0E0E0',
269
+ },
270
+ headerCell: {
271
+ padding: 12,
272
+ borderRightWidth: 1,
273
+ borderRightColor: '#E0E0E0',
274
+ justifyContent: 'center',
275
+ },
276
+ headerText: {
277
+ fontSize: 14,
278
+ fontWeight: '600',
279
+ color: '#2C3E50',
280
+ },
281
+ row: {
282
+ flexDirection: 'row',
283
+ borderBottomWidth: 1,
284
+ borderBottomColor: '#F0F0F0',
285
+ backgroundColor: '#FFFFFF',
286
+ },
287
+ statsRow: {
288
+ backgroundColor: '#F9F9F9',
289
+ },
290
+ cell: {
291
+ padding: 12,
292
+ justifyContent: 'center',
293
+ borderRightWidth: 1,
294
+ borderRightColor: '#F0F0F0',
295
+ },
296
+ rowLabel: {
297
+ fontSize: 14,
298
+ fontWeight: '500',
299
+ color: '#2C3E50',
300
+ },
301
+ cellQuantity: {
302
+ fontSize: 14,
303
+ fontWeight: '500',
304
+ color: '#2C3E50',
305
+ marginBottom: 4,
306
+ },
307
+ cellAmount: {
308
+ fontSize: 12,
309
+ color: '#7F8C8D',
310
+ },
311
+ statsLabel: {
312
+ fontSize: 14,
313
+ fontWeight: '600',
314
+ color: '#2C3E50',
315
+ },
316
+ loadingRow: {
317
+ padding: 40,
318
+ justifyContent: 'center',
319
+ alignItems: 'center',
320
+ },
321
+ emptyRow: {
322
+ padding: 40,
323
+ justifyContent: 'center',
324
+ alignItems: 'center',
325
+ },
326
+ emptyText: {
327
+ fontSize: 14,
328
+ color: '#999',
329
+ },
330
+ paginationContainer: {
331
+ padding: 16,
332
+ borderTopWidth: 1,
333
+ borderTopColor: '#E0E0E0',
334
+ backgroundColor: '#FAFAFA',
335
+ },
336
+ });
337
+
@@ -0,0 +1,7 @@
1
+ export { StatisticsTable } from './StatisticsTable';
2
+ export type {
3
+ StatisticsTableProps,
4
+ StatisticsTableColumn,
5
+ StatisticsTableCell
6
+ } from './StatisticsTable';
7
+
@@ -175,6 +175,8 @@ export { DataTable } from "./DataTable";
175
175
  export type { DataTableProps, DataTableColumn } from "./DataTable";
176
176
  export { StatisticCard } from "./StatisticCard";
177
177
  export type { StatisticCardProps } from "./StatisticCard";
178
+ export { StatisticsTable } from "./StatisticsTable";
179
+ export type { StatisticsTableProps, StatisticsTableColumn, StatisticsTableCell } from "./StatisticsTable";
178
180
 
179
181
  // 工具函数
180
182
  export { isMobile, capitalizeFirstLetter } from "./common";