feat: usage filter & export & dashbord (#3538)
* feat: usage filter & export & dashbord * adjust ui * fix tmb scroll * fix code & selecte all * merge
This commit is contained in:
parent
e009be51e7
commit
0c05add8b2
17
packages/global/support/wallet/usage/api.d.ts
vendored
17
packages/global/support/wallet/usage/api.d.ts
vendored
@ -6,6 +6,23 @@ export type CreateTrainingUsageProps = {
|
||||
datasetId: string;
|
||||
};
|
||||
|
||||
export type GetTotalPointsProps = {
|
||||
dateStart: Date;
|
||||
dateEnd: Date;
|
||||
teamMemberIds: string[];
|
||||
sources: UsageSourceEnum[];
|
||||
unit: 'day' | 'week' | 'month';
|
||||
};
|
||||
|
||||
export type GetUsageProps = {
|
||||
dateStart: Date;
|
||||
dateEnd: Date;
|
||||
sources?: UsageSourceEnum[];
|
||||
teamMemberIds?: string[];
|
||||
projectName?: string;
|
||||
isSelectAllTmb?: boolean;
|
||||
};
|
||||
|
||||
export type ConcatUsageProps = UsageListItemCountType & {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
|
||||
@ -18,30 +18,30 @@ export const UsageSourceMap = {
|
||||
label: i18nT('common:core.chat.logs.online')
|
||||
},
|
||||
[UsageSourceEnum.api]: {
|
||||
label: 'Api'
|
||||
label: 'API'
|
||||
},
|
||||
[UsageSourceEnum.shareLink]: {
|
||||
label: i18nT('common:core.chat.logs.free_login')
|
||||
},
|
||||
[UsageSourceEnum.training]: {
|
||||
label: 'dataset.Training Name'
|
||||
label: i18nT('common:dataset.Training Name')
|
||||
},
|
||||
[UsageSourceEnum.cronJob]: {
|
||||
label: i18nT('common:cron_job_run_app')
|
||||
},
|
||||
[UsageSourceEnum.feishu]: {
|
||||
label: i18nT('user:usage.feishu')
|
||||
label: i18nT('account_usage:feishu')
|
||||
},
|
||||
[UsageSourceEnum.official_account]: {
|
||||
label: i18nT('user:usage.official_account')
|
||||
label: i18nT('account_usage:official_account')
|
||||
},
|
||||
[UsageSourceEnum.share]: {
|
||||
label: i18nT('user:usage.share')
|
||||
label: i18nT('account_usage:share')
|
||||
},
|
||||
[UsageSourceEnum.wecom]: {
|
||||
label: i18nT('user:usage.wecom')
|
||||
label: i18nT('account_usage:wecom')
|
||||
},
|
||||
[UsageSourceEnum.dingtalk]: {
|
||||
label: i18nT('user:usage.dingtalk')
|
||||
label: i18nT('account_usage:dingtalk')
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { SourceMemberType } from '../../../support/user/type';
|
||||
import { CreateUsageProps } from './api';
|
||||
import { UsageSourceEnum } from './constants';
|
||||
|
||||
@ -10,6 +11,7 @@ export type UsageListItemCountType = {
|
||||
// deprecated
|
||||
tokens?: number;
|
||||
};
|
||||
|
||||
export type UsageListItemType = UsageListItemCountType & {
|
||||
moduleName: string;
|
||||
amount: number;
|
||||
@ -28,4 +30,5 @@ export type UsageItemType = {
|
||||
source: UsageSchemaType['source'];
|
||||
totalPoints: number;
|
||||
list: UsageSchemaType['list'];
|
||||
sourceMember: SourceMemberType;
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useMemo, useRef } from 'react';
|
||||
import React, { useState, useMemo, useRef, useEffect } from 'react';
|
||||
import { Box, Card, Flex, useTheme, useOutsideClick, Button } from '@chakra-ui/react';
|
||||
import { addDays, format } from 'date-fns';
|
||||
import { type DateRange, DayPicker } from 'react-day-picker';
|
||||
@ -14,12 +14,14 @@ const DateRangePicker = ({
|
||||
defaultDate = {
|
||||
from: addDays(new Date(), -30),
|
||||
to: new Date()
|
||||
}
|
||||
},
|
||||
dateRange
|
||||
}: {
|
||||
onChange?: (date: DateRange) => void;
|
||||
onSuccess?: (date: DateRange) => void;
|
||||
position?: 'bottom' | 'top';
|
||||
defaultDate?: DateRange;
|
||||
dateRange?: DateRange;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
@ -27,6 +29,12 @@ const DateRangePicker = ({
|
||||
const [range, setRange] = useState<DateRange | undefined>(defaultDate);
|
||||
const [showSelected, setShowSelected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (dateRange) {
|
||||
setRange(dateRange);
|
||||
}
|
||||
}, [dateRange]);
|
||||
|
||||
const formatSelected = useMemo(() => {
|
||||
if (range?.from && range.to) {
|
||||
return `${format(range.from, 'y-MM-dd')} ~ ${format(range.to, 'y-MM-dd')}`;
|
||||
@ -49,7 +57,7 @@ const DateRangePicker = ({
|
||||
py={1}
|
||||
borderRadius={'sm'}
|
||||
cursor={'pointer'}
|
||||
bg={'myGray.100'}
|
||||
bg={'myGray.50'}
|
||||
fontSize={'sm'}
|
||||
onClick={() => setShowSelected(true)}
|
||||
>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ButtonProps,
|
||||
Checkbox,
|
||||
Flex,
|
||||
Menu,
|
||||
MenuButton,
|
||||
@ -10,11 +10,12 @@ import {
|
||||
MenuList,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useRef } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import MyTag from '../Tag/index';
|
||||
import MyIcon from '../Icon';
|
||||
import MyAvatar from '../Avatar';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useScrollPagination } from '../../../hooks/useScrollPagination';
|
||||
|
||||
export type SelectProps<T = any> = {
|
||||
value: T[];
|
||||
@ -25,22 +26,31 @@ export type SelectProps<T = any> = {
|
||||
value: T;
|
||||
}[];
|
||||
maxH?: number;
|
||||
itemWrap?: boolean;
|
||||
onSelect: (val: T[]) => void;
|
||||
closeable?: boolean;
|
||||
showCheckedIcon?: boolean;
|
||||
ScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
|
||||
isSelectAll?: boolean;
|
||||
setIsSelectAll?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
} & Omit<ButtonProps, 'onSelect'>;
|
||||
|
||||
const MultipleSelect = <T = any,>({
|
||||
value = [],
|
||||
placeholder,
|
||||
list = [],
|
||||
width = '100%',
|
||||
maxH = 400,
|
||||
onSelect,
|
||||
closeable = false,
|
||||
showCheckedIcon = true,
|
||||
itemWrap = true,
|
||||
ScrollData,
|
||||
isSelectAll,
|
||||
setIsSelectAll,
|
||||
...props
|
||||
}: SelectProps<T>) => {
|
||||
const { t } = useTranslation();
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const menuItemStyles: MenuItemProps = {
|
||||
borderRadius: 'sm',
|
||||
@ -63,6 +73,71 @@ const MultipleSelect = <T = any,>({
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectAll = () => {
|
||||
if (!setIsSelectAll) {
|
||||
onSelect(value.length === list.length ? [] : list.map((item) => item.value));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelectAll) {
|
||||
onSelect([]);
|
||||
}
|
||||
setIsSelectAll((state) => !state);
|
||||
};
|
||||
|
||||
const ListRender = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{list.map((item, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
{...menuItemStyles}
|
||||
{...((isSelectAll && !value.includes(item.value)) ||
|
||||
(!isSelectAll && value.includes(item.value))
|
||||
? {
|
||||
color: 'primary.600'
|
||||
}
|
||||
: {
|
||||
color: 'myGray.900'
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onclickItem(item.value);
|
||||
}}
|
||||
whiteSpace={'pre-wrap'}
|
||||
fontSize={'sm'}
|
||||
gap={2}
|
||||
>
|
||||
{!showCheckedIcon && (
|
||||
<Checkbox
|
||||
isChecked={
|
||||
(isSelectAll && !value.includes(item.value)) ||
|
||||
(!isSelectAll && value.includes(item.value))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{item.icon && <MyAvatar src={item.icon} w={'1rem'} borderRadius={'0'} />}
|
||||
<Box flex={'1 0 0'}>{item.label}</Box>
|
||||
{showCheckedIcon && (
|
||||
<Box w={'0.8rem'} lineHeight={1}>
|
||||
{(isSelectAll && !value.includes(item.value)) ||
|
||||
(!isSelectAll && value.includes(item.value) && (
|
||||
<MyIcon name={'price/right'} w={'1rem'} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}, [value, list, isSelectAll]);
|
||||
|
||||
const isAllSelected = useMemo(
|
||||
() => (isSelectAll && value.length === 0) || (!isSelectAll && value.length === list.length),
|
||||
[isSelectAll, value, list]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Menu
|
||||
@ -75,12 +150,10 @@ const MultipleSelect = <T = any,>({
|
||||
closeOnSelect={false}
|
||||
>
|
||||
<MenuButton
|
||||
as={Box}
|
||||
as={Flex}
|
||||
alignItems={'center'}
|
||||
ref={ref}
|
||||
width={width}
|
||||
minH={'40px'}
|
||||
px={3}
|
||||
py={2}
|
||||
borderRadius={'md'}
|
||||
border={'base'}
|
||||
userSelect={'none'}
|
||||
@ -88,6 +161,9 @@ const MultipleSelect = <T = any,>({
|
||||
_active={{
|
||||
transform: 'none'
|
||||
}}
|
||||
_hover={{
|
||||
borderColor: 'primary.300'
|
||||
}}
|
||||
{...props}
|
||||
{...(isOpen
|
||||
? {
|
||||
@ -102,82 +178,94 @@ const MultipleSelect = <T = any,>({
|
||||
{placeholder}
|
||||
</Box>
|
||||
) : (
|
||||
<Flex alignItems={'center'} gap={2} flexWrap={'wrap'}>
|
||||
{value.map((item, i) => {
|
||||
const listItem = list.find((i) => i.value === item);
|
||||
if (!listItem) return null;
|
||||
|
||||
return (
|
||||
<MyTag className="tag-icon" key={i} colorSchema="blue" type={'borderFill'}>
|
||||
{listItem.label}
|
||||
{closeable && (
|
||||
<MyIcon
|
||||
name={'common/closeLight'}
|
||||
ml={1}
|
||||
w="0.8rem"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.500'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
console.log(111);
|
||||
e.stopPropagation();
|
||||
onclickItem(item);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MyTag>
|
||||
);
|
||||
})}
|
||||
<Flex alignItems={'center'} gap={2}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
gap={2}
|
||||
flexWrap={itemWrap ? 'wrap' : 'nowrap'}
|
||||
overflow={'hidden'}
|
||||
flex={1}
|
||||
>
|
||||
{isAllSelected ? (
|
||||
<Box fontSize={'mini'} color={'myGray.900'}>
|
||||
{t('common:common.All')}
|
||||
</Box>
|
||||
) : (
|
||||
(isSelectAll
|
||||
? list.filter((item) => !value.includes(item.value))
|
||||
: list.filter((item) => value.includes(item.value))
|
||||
).map((item, i) => (
|
||||
<MyTag
|
||||
className="tag-icon"
|
||||
key={i}
|
||||
bg={'primary.100'}
|
||||
color={'primary.700'}
|
||||
type={'fill'}
|
||||
borderRadius={'full'}
|
||||
px={2}
|
||||
py={0.5}
|
||||
flexShrink={0}
|
||||
>
|
||||
{item.label}
|
||||
{closeable && (
|
||||
<MyIcon
|
||||
name={'common/closeLight'}
|
||||
ml={1}
|
||||
w="0.8rem"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.500'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onclickItem(item.value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MyTag>
|
||||
))
|
||||
)}
|
||||
</Flex>
|
||||
<MyIcon name={'core/chat/chevronDown'} color={'myGray.600'} w={4} h={4} />
|
||||
</Flex>
|
||||
)}
|
||||
</MenuButton>
|
||||
|
||||
<MenuList
|
||||
className={props.className}
|
||||
minW={(() => {
|
||||
const w = ref.current?.clientWidth;
|
||||
if (w) {
|
||||
return `${w}px !important`;
|
||||
}
|
||||
return Array.isArray(width)
|
||||
? width.map((item) => `${item} !important`)
|
||||
: `${width} !important`;
|
||||
})()}
|
||||
w={'auto'}
|
||||
px={'6px'}
|
||||
py={'6px'}
|
||||
border={'1px solid #fff'}
|
||||
boxShadow={
|
||||
'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'
|
||||
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10);'
|
||||
}
|
||||
zIndex={99}
|
||||
maxH={'40vh'}
|
||||
overflowY={'auto'}
|
||||
>
|
||||
{list.map((item, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
{...menuItemStyles}
|
||||
{...(value.includes(item.value)
|
||||
? {
|
||||
color: 'primary.600'
|
||||
}
|
||||
: {
|
||||
color: 'myGray.900'
|
||||
})}
|
||||
onClick={() => onclickItem(item.value)}
|
||||
whiteSpace={'pre-wrap'}
|
||||
fontSize={'sm'}
|
||||
gap={2}
|
||||
>
|
||||
{item.icon && <MyAvatar src={item.icon} w={'1rem'} borderRadius={'0'} />}
|
||||
<Box flex={'1 0 0'}>{item.label}</Box>
|
||||
<MenuItem
|
||||
{...menuItemStyles}
|
||||
color={isAllSelected ? 'primary.600' : 'myGray.900'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onSelectAll();
|
||||
}}
|
||||
whiteSpace={'pre-wrap'}
|
||||
fontSize={'sm'}
|
||||
gap={2}
|
||||
mb={1}
|
||||
>
|
||||
{!showCheckedIcon && <Checkbox isChecked={isAllSelected} />}
|
||||
<Box flex={'1 0 0'}>{t('common:common.All')}</Box>
|
||||
{showCheckedIcon && (
|
||||
<Box w={'0.8rem'} lineHeight={1}>
|
||||
{value.includes(item.value) && <MyIcon name={'price/right'} w={'1rem'} />}
|
||||
{isAllSelected && <MyIcon name={'price/right'} w={'1rem'} />}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))}
|
||||
)}
|
||||
</MenuItem>
|
||||
|
||||
{ScrollData ? <ScrollData>{ListRender}</ScrollData> : ListRender}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Box>
|
||||
|
||||
@ -66,7 +66,7 @@ const MyTag = ({ children, colorSchema = 'blue', type = 'fill', showDot, ...prop
|
||||
}, [colorSchema]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
<Flex
|
||||
display={'inline-flex'}
|
||||
px={2.5}
|
||||
lineHeight={1}
|
||||
@ -83,7 +83,7 @@ const MyTag = ({ children, colorSchema = 'blue', type = 'fill', showDot, ...prop
|
||||
>
|
||||
{showDot && <Box w={1.5} h={1.5} borderRadius={'md'} bg={theme.color} mr={1.5}></Box>}
|
||||
{children}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -3,8 +3,14 @@
|
||||
"all": "all",
|
||||
"app_name": "Application name",
|
||||
"billing_module": "Deduction module",
|
||||
"confirm_export": "A total of {{total}} pieces of data were filtered out. Are you sure to export?",
|
||||
"current_filter_conditions": "Current filter conditions",
|
||||
"dashboard": "Dashboard",
|
||||
"details": "Details",
|
||||
"dingtalk": "DingTalk",
|
||||
"duration_seconds": "Duration (seconds)",
|
||||
"export_confirm": "Export confirmation",
|
||||
"feishu": "Feishu",
|
||||
"generation_time": "Generation time",
|
||||
"input_token_length": "input tokens",
|
||||
"member": "member",
|
||||
@ -12,14 +18,21 @@
|
||||
"module_name": "module name",
|
||||
"month": "moon",
|
||||
"no_usage_records": "No usage record yet",
|
||||
"official_account": "Official Account",
|
||||
"order_number": "Order number",
|
||||
"output_token_length": "output tokens",
|
||||
"points": "Points",
|
||||
"project_name": "Project name",
|
||||
"select_member_and_source_first": "Please select members and types first",
|
||||
"share": "Share Link",
|
||||
"source": "source",
|
||||
"start_export": "Export started",
|
||||
"text_length": "text length",
|
||||
"token_length": "token length",
|
||||
"total_points": "AI points consumption",
|
||||
"total_points_consumed": "AI points consumption",
|
||||
"usage_detail": "Usage details",
|
||||
"user_type": "type"
|
||||
"total_usage": "Total Usage",
|
||||
"usage_detail": "Details",
|
||||
"user_type": "type",
|
||||
"wecom": "WeCom"
|
||||
}
|
||||
|
||||
@ -112,10 +112,5 @@
|
||||
"team.org.org": "Organization",
|
||||
"team.manage_collaborators": "Manage Collaborators",
|
||||
"team.no_collaborators": "No Collaborators",
|
||||
"team.write_role_member": "",
|
||||
"usage.feishu": "Feishu",
|
||||
"usage.dingtalk": "DingTalk",
|
||||
"usage.official_account": "Official Account",
|
||||
"usage.share": "Share Link",
|
||||
"usage.wecom": "WeCom"
|
||||
"team.write_role_member": ""
|
||||
}
|
||||
|
||||
@ -3,8 +3,18 @@
|
||||
"all": "所有",
|
||||
"app_name": "应用名",
|
||||
"billing_module": "扣费模块",
|
||||
"confirm_export": "共筛选出 {{total}} 条数据,是否确认导出?",
|
||||
"current_filter_conditions": "当前筛选条件:",
|
||||
"dashboard": "仪表盘",
|
||||
"details": "详情",
|
||||
"dingtalk": "钉钉",
|
||||
"duration_seconds": "时长(秒)",
|
||||
"every_day": "每天",
|
||||
"every_month": "每月",
|
||||
"every_week": "每周",
|
||||
"export_confirm": "导出确认",
|
||||
"export_success": "导出成功",
|
||||
"feishu": "飞书",
|
||||
"generation_time": "生成时间",
|
||||
"input_token_length": "输入 tokens",
|
||||
"member": "成员",
|
||||
@ -12,14 +22,21 @@
|
||||
"module_name": "模块名",
|
||||
"month": "月",
|
||||
"no_usage_records": "暂无使用记录",
|
||||
"official_account": "公众号",
|
||||
"order_number": "订单号",
|
||||
"output_token_length": "输出 tokens",
|
||||
"points": "积分",
|
||||
"project_name": "项目名",
|
||||
"select_member_and_source_first": "请先选中成员和类型",
|
||||
"share": "分享链接",
|
||||
"source": "来源",
|
||||
"start_export": "已开始导出",
|
||||
"text_length": "文本长度",
|
||||
"token_length": "token 长度",
|
||||
"total_points": "AI 积分消耗",
|
||||
"total_points_consumed": "AI 积分消耗",
|
||||
"total_usage": "总消耗",
|
||||
"usage_detail": "使用详情",
|
||||
"user_type": "类型"
|
||||
"user_type": "类型",
|
||||
"wecom": "企业微信"
|
||||
}
|
||||
|
||||
@ -112,10 +112,5 @@
|
||||
"team.org.org": "部门",
|
||||
"team.manage_collaborators": "管理协作者",
|
||||
"team.no_collaborators": "暂无协作者",
|
||||
"team.write_role_member": "可写权限",
|
||||
"usage.feishu": "飞书",
|
||||
"usage.dingtalk": "钉钉",
|
||||
"usage.official_account": "公众号",
|
||||
"usage.share": "分享链接",
|
||||
"usage.wecom": "企业微信"
|
||||
"team.write_role_member": "可写权限"
|
||||
}
|
||||
|
||||
@ -3,8 +3,14 @@
|
||||
"all": "所有",
|
||||
"app_name": "應用程式名",
|
||||
"billing_module": "扣費模組",
|
||||
"confirm_export": "共篩選出 {{total}} 條數據,是否確認導出?",
|
||||
"current_filter_conditions": "當前篩選條件:",
|
||||
"dashboard": "儀表板",
|
||||
"details": "詳情",
|
||||
"dingtalk": "釘釘",
|
||||
"duration_seconds": "時長(秒)",
|
||||
"export_confirm": "導出確認",
|
||||
"feishu": "飛書",
|
||||
"generation_time": "生成時間",
|
||||
"input_token_length": "輸入 tokens",
|
||||
"member": "成員",
|
||||
@ -12,14 +18,21 @@
|
||||
"module_name": "模組名",
|
||||
"month": "月",
|
||||
"no_usage_records": "暫無使用紀錄",
|
||||
"official_account": "公眾號",
|
||||
"order_number": "訂單編號",
|
||||
"output_token_length": "輸出 tokens",
|
||||
"points": "積分",
|
||||
"project_name": "專案名",
|
||||
"select_member_and_source_first": "請先選取成員和類型",
|
||||
"share": "分享連結",
|
||||
"source": "來源",
|
||||
"start_export": "已開始匯出",
|
||||
"text_length": "文字長度",
|
||||
"token_length": "token 長度",
|
||||
"total_points": "AI 積分消耗",
|
||||
"total_points_consumed": "AI 積分消耗",
|
||||
"total_usage": "總消耗",
|
||||
"usage_detail": "使用詳情",
|
||||
"user_type": "類型"
|
||||
"user_type": "類型",
|
||||
"wecom": "企業微信"
|
||||
}
|
||||
|
||||
@ -112,10 +112,5 @@
|
||||
"team.org.org": "組織",
|
||||
"team.manage_collaborators": "管理協作者",
|
||||
"team.no_collaborators": "目前沒有協作者",
|
||||
"team.write_role_member": "可寫入權限",
|
||||
"usage.feishu": "飛書",
|
||||
"usage.dingtalk": "釘釘",
|
||||
"usage.official_account": "公眾號",
|
||||
"usage.share": "分享連結",
|
||||
"usage.wecom": "企業微信"
|
||||
"team.write_role_member": "可寫入權限"
|
||||
}
|
||||
|
||||
199
pnpm-lock.yaml
generated
199
pnpm-lock.yaml
generated
@ -22,7 +22,7 @@ importers:
|
||||
version: 13.3.0
|
||||
next-i18next:
|
||||
specifier: 15.3.0
|
||||
version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
version: 15.3.0(i18next@23.11.5)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
prettier:
|
||||
specifier: 3.2.4
|
||||
version: 3.2.4
|
||||
@ -67,7 +67,7 @@ importers:
|
||||
version: 4.0.2
|
||||
next:
|
||||
specifier: 14.2.5
|
||||
version: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
|
||||
version: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
|
||||
openai:
|
||||
specifier: 4.61.0
|
||||
version: 4.61.0(encoding@0.1.13)
|
||||
@ -210,7 +210,7 @@ importers:
|
||||
version: 1.4.5-lts.1
|
||||
next:
|
||||
specifier: 14.2.5
|
||||
version: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
|
||||
version: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
|
||||
nextjs-cors:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))
|
||||
@ -295,7 +295,7 @@ importers:
|
||||
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@chakra-ui/next-js':
|
||||
specifier: 2.1.5
|
||||
version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
|
||||
version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
|
||||
'@chakra-ui/react':
|
||||
specifier: 2.8.1
|
||||
version: 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@ -358,7 +358,7 @@ importers:
|
||||
version: 4.17.21
|
||||
next-i18next:
|
||||
specifier: 15.3.0
|
||||
version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
version: 15.3.0(i18next@23.11.5)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
papaparse:
|
||||
specifier: ^5.4.1
|
||||
version: 5.4.1
|
||||
@ -558,6 +558,9 @@ importers:
|
||||
reactflow:
|
||||
specifier: ^11.7.4
|
||||
version: 11.11.4(@types/react@18.3.1)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
recharts:
|
||||
specifier: ^2.15.0
|
||||
version: 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
rehype-external-links:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@ -3198,8 +3201,8 @@ packages:
|
||||
'@tanstack/react-query@4.36.1':
|
||||
resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1
|
||||
react-native: '*'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
@ -4330,6 +4333,10 @@ packages:
|
||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
co@4.6.0:
|
||||
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||
@ -4760,6 +4767,9 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decimal.js-light@2.5.1:
|
||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||
|
||||
decode-named-character-reference@1.0.2:
|
||||
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
|
||||
|
||||
@ -4902,6 +4912,9 @@ packages:
|
||||
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||
|
||||
dom-serializer@1.4.1:
|
||||
resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
|
||||
|
||||
@ -5215,6 +5228,9 @@ packages:
|
||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
eventemitter3@4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
|
||||
eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
|
||||
@ -5272,6 +5288,10 @@ packages:
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
fast-equals@5.2.0:
|
||||
resolution: {integrity: sha512-3VpaQYf+CDFdRQfgsb+3vY7XaKjM35WCMoQTTE8h4S/eUkHzyJFOOA/gATYgoLejy4FBrEQD/sXe5Auk4cW/AQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
fast-fifo@1.3.2:
|
||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||
|
||||
@ -7828,6 +7848,12 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-smooth@4.0.4:
|
||||
resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
react-style-singleton@2.2.1:
|
||||
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
||||
engines: {node: '>=10'}
|
||||
@ -7849,6 +7875,12 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
react-transition-group@4.4.5:
|
||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||
peerDependencies:
|
||||
react: '>=16.6.0'
|
||||
react-dom: '>=16.6.0'
|
||||
|
||||
react@18.3.1:
|
||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -7878,6 +7910,16 @@ packages:
|
||||
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||
engines: {node: '>= 12.13.0'}
|
||||
|
||||
recharts-scale@0.4.5:
|
||||
resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
|
||||
|
||||
recharts@2.15.0:
|
||||
resolution: {integrity: sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
redux@4.2.1:
|
||||
resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
|
||||
|
||||
@ -8978,6 +9020,9 @@ packages:
|
||||
vfile@6.0.2:
|
||||
resolution: {integrity: sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==}
|
||||
|
||||
victory-vendor@36.9.2:
|
||||
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
|
||||
|
||||
vite-node@1.6.0:
|
||||
resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
@ -10492,6 +10537,14 @@ snapshots:
|
||||
next: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
|
||||
react: 18.3.1
|
||||
|
||||
'@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@chakra-ui/react': 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@emotion/cache': 11.11.0
|
||||
'@emotion/react': 11.11.1(@types/react@18.3.1)(react@18.3.1)
|
||||
next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
|
||||
react: 18.3.1
|
||||
|
||||
'@chakra-ui/number-input@2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@chakra-ui/counter': 2.1.0(react@18.3.1)
|
||||
@ -13231,7 +13284,7 @@ snapshots:
|
||||
|
||||
axios@1.7.7:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9(debug@4.3.7)
|
||||
follow-redirects: 1.15.9
|
||||
form-data: 4.0.1
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
@ -13637,6 +13690,8 @@ snapshots:
|
||||
|
||||
clone@1.0.4: {}
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
co@4.6.0: {}
|
||||
|
||||
collapse-white-space@1.0.6: {}
|
||||
@ -14078,6 +14133,8 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decimal.js-light@2.5.1: {}
|
||||
|
||||
decode-named-character-reference@1.0.2:
|
||||
dependencies:
|
||||
character-entities: 2.0.2
|
||||
@ -14234,6 +14291,11 @@ snapshots:
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.25.7
|
||||
csstype: 3.1.3
|
||||
|
||||
dom-serializer@1.4.1:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
@ -14731,6 +14793,8 @@ snapshots:
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
|
||||
eventemitter3@4.0.7: {}
|
||||
|
||||
eventemitter3@5.0.1: {}
|
||||
|
||||
events@3.3.0: {}
|
||||
@ -14837,6 +14901,8 @@ snapshots:
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-equals@5.2.0: {}
|
||||
|
||||
fast-fifo@1.3.2: {}
|
||||
|
||||
fast-glob@3.3.2:
|
||||
@ -15012,6 +15078,8 @@ snapshots:
|
||||
|
||||
follow-redirects@1.15.6: {}
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
|
||||
follow-redirects@1.15.9(debug@4.3.4):
|
||||
optionalDependencies:
|
||||
debug: 4.3.4
|
||||
@ -17359,6 +17427,18 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-i18next: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
next-i18next@15.3.0(i18next@23.11.5)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.8
|
||||
'@types/hoist-non-react-statics': 3.3.5
|
||||
core-js: 3.37.1
|
||||
hoist-non-react-statics: 3.3.2
|
||||
i18next: 23.11.5
|
||||
i18next-fs-backend: 2.3.1
|
||||
next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
|
||||
react: 18.3.1
|
||||
react-i18next: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8):
|
||||
dependencies:
|
||||
'@next/env': 14.2.5
|
||||
@ -17385,10 +17465,36 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8):
|
||||
dependencies:
|
||||
'@next/env': 14.2.5
|
||||
'@swc/helpers': 0.5.5
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: 1.0.30001669
|
||||
graceful-fs: 4.2.11
|
||||
postcss: 8.4.31
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
styled-jsx: 5.1.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 14.2.5
|
||||
'@next/swc-darwin-x64': 14.2.5
|
||||
'@next/swc-linux-arm64-gnu': 14.2.5
|
||||
'@next/swc-linux-arm64-musl': 14.2.5
|
||||
'@next/swc-linux-x64-gnu': 14.2.5
|
||||
'@next/swc-linux-x64-musl': 14.2.5
|
||||
'@next/swc-win32-arm64-msvc': 14.2.5
|
||||
'@next/swc-win32-ia32-msvc': 14.2.5
|
||||
'@next/swc-win32-x64-msvc': 14.2.5
|
||||
sass: 1.77.8
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
nextjs-cors@2.2.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)):
|
||||
dependencies:
|
||||
cors: 2.8.5
|
||||
next: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
|
||||
next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
|
||||
|
||||
nextjs-node-loader@1.1.5(webpack@5.92.1):
|
||||
dependencies:
|
||||
@ -18097,6 +18203,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.1
|
||||
|
||||
react-smooth@4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
fast-equals: 5.2.0
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
react-style-singleton@2.2.1(@types/react@18.3.1)(react@18.3.1):
|
||||
dependencies:
|
||||
get-nonce: 1.0.1
|
||||
@ -18124,6 +18238,15 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.25.7
|
||||
dom-helpers: 5.2.1
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
react@18.3.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
@ -18172,6 +18295,23 @@ snapshots:
|
||||
|
||||
real-require@0.2.0: {}
|
||||
|
||||
recharts-scale@0.4.5:
|
||||
dependencies:
|
||||
decimal.js-light: 2.5.1
|
||||
|
||||
recharts@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
eventemitter3: 4.0.7
|
||||
lodash: 4.17.21
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-is: 18.3.1
|
||||
react-smooth: 4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
recharts-scale: 0.4.5
|
||||
tiny-invariant: 1.3.3
|
||||
victory-vendor: 36.9.2
|
||||
|
||||
redux@4.2.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.8
|
||||
@ -18791,6 +18931,11 @@ snapshots:
|
||||
'@babel/core': 7.24.9
|
||||
babel-plugin-macros: 3.1.0
|
||||
|
||||
styled-jsx@5.1.1(react@18.3.1):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 18.3.1
|
||||
|
||||
stylis@4.2.0: {}
|
||||
|
||||
stylis@4.3.2: {}
|
||||
@ -18992,6 +19137,25 @@ snapshots:
|
||||
|
||||
ts-dedent@2.2.0: {}
|
||||
|
||||
ts-jest@29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3):
|
||||
dependencies:
|
||||
bs-logger: 0.2.6
|
||||
ejs: 3.1.10
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
jest: 29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3))
|
||||
jest-util: 29.7.0
|
||||
json5: 2.2.3
|
||||
lodash.memoize: 4.1.2
|
||||
make-error: 1.3.6
|
||||
semver: 7.6.3
|
||||
typescript: 5.5.3
|
||||
yargs-parser: 21.1.1
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.24.9
|
||||
'@jest/transform': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
babel-jest: 29.7.0(@babel/core@7.24.9)
|
||||
|
||||
ts-jest@29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3):
|
||||
dependencies:
|
||||
bs-logger: 0.2.6
|
||||
@ -19377,6 +19541,23 @@ snapshots:
|
||||
unist-util-stringify-position: 4.0.0
|
||||
vfile-message: 4.0.2
|
||||
|
||||
victory-vendor@36.9.2:
|
||||
dependencies:
|
||||
'@types/d3-array': 3.2.1
|
||||
'@types/d3-ease': 3.0.2
|
||||
'@types/d3-interpolate': 3.0.4
|
||||
'@types/d3-scale': 4.0.8
|
||||
'@types/d3-shape': 3.1.6
|
||||
'@types/d3-time': 3.0.3
|
||||
'@types/d3-timer': 3.0.2
|
||||
d3-array: 3.2.4
|
||||
d3-ease: 3.0.1
|
||||
d3-interpolate: 3.0.1
|
||||
d3-scale: 4.0.2
|
||||
d3-shape: 3.2.0
|
||||
d3-time: 3.1.0
|
||||
d3-timer: 3.0.1
|
||||
|
||||
vite-node@1.6.0(@types/node@22.7.8)(sass@1.77.8)(terser@5.31.3):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
"@fastgpt/global": "workspace:*",
|
||||
"@fastgpt/plugins": "workspace:*",
|
||||
"@fastgpt/service": "workspace:*",
|
||||
"@fastgpt/web": "workspace:*",
|
||||
"@fastgpt/templates": "workspace:*",
|
||||
"@fastgpt/web": "workspace:*",
|
||||
"@fortaine/fetch-event-source": "^3.0.6",
|
||||
"@node-rs/jieba": "1.10.0",
|
||||
"@tanstack/react-query": "^4.24.10",
|
||||
@ -60,6 +60,7 @@
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-textarea-autosize": "^8.5.4",
|
||||
"reactflow": "^11.7.4",
|
||||
"recharts": "^2.15.0",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"rehype-katex": "^7.0.0",
|
||||
"remark-breaks": "^4.0.0",
|
||||
|
||||
@ -32,6 +32,7 @@ import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
|
||||
const BillTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
@ -177,6 +178,7 @@ export default BillTable;
|
||||
|
||||
function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
import { downloadFetch } from '@/web/common/system/utils';
|
||||
import { Button, Flex, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import { formatTime2YMD } from '@fastgpt/global/common/string/time';
|
||||
import { UsageSourceEnum, UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
export type ExportModalParams = {
|
||||
dateStart: Date;
|
||||
dateEnd: Date;
|
||||
sources: UsageSourceEnum[];
|
||||
teamMemberIds: string[];
|
||||
teamMemberNames: string[];
|
||||
isSelectAllTmb: boolean;
|
||||
projectName: string;
|
||||
};
|
||||
|
||||
const ExportModal = ({
|
||||
onClose,
|
||||
params,
|
||||
memberTotal,
|
||||
total
|
||||
}: {
|
||||
onClose: () => void;
|
||||
params: ExportModalParams;
|
||||
memberTotal: number;
|
||||
total: number;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
teamMemberIds,
|
||||
teamMemberNames,
|
||||
isSelectAllTmb,
|
||||
sources,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
projectName
|
||||
} = params;
|
||||
|
||||
const { runAsync: exportUsage, loading } = useRequest2(
|
||||
async () => {
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('dateStart', dateStart.toISOString());
|
||||
searchParams.set('dateEnd', dateEnd.toISOString());
|
||||
sources.forEach((source) => searchParams.append('sources', source.toString()));
|
||||
teamMemberIds.forEach((tmbId) => searchParams.append('teamMemberIds', tmbId));
|
||||
searchParams.set('isSelectAllTmb', isSelectAllTmb.toString());
|
||||
searchParams.set('projectName', projectName);
|
||||
|
||||
await downloadFetch({
|
||||
url: `/api/proApi/support/wallet/usage/exportUsage?${searchParams}`,
|
||||
filename: `usage.csv`
|
||||
});
|
||||
},
|
||||
{
|
||||
successToast: t('account_usage:start_export')
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal title={t('account_usage:export_confirm')} iconSrc="export" iconColor={'primary.600'}>
|
||||
<ModalBody>
|
||||
<Flex mb={4}>{t('account_usage:current_filter_conditions')}</Flex>
|
||||
<Flex>
|
||||
{`${t('common:user.Time')}: ${formatTime2YMD(dateStart)} ~ ${formatTime2YMD(dateEnd)}`}
|
||||
</Flex>
|
||||
<Flex>{`${t('common:user.team.Member')}(${memberTotal}): ${teamMemberNames.join(', ')}`}</Flex>
|
||||
<Flex>
|
||||
{`${t('common:user.type')}: ${sources.map((item) => t(UsageSourceMap[item].label as any)).join(', ')}`}
|
||||
</Flex>
|
||||
<Flex>{`${t('common:user.Application Name')}: ${projectName}`}</Flex>
|
||||
<Flex mt={4}>{t('account_usage:confirm_export', { total })}</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter gap={2}>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button onClick={exportUsage} isLoading={loading}>
|
||||
{t('common:Export')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExportModal;
|
||||
164
projects/app/src/pages/account/usage/components/UsageForm.tsx
Normal file
164
projects/app/src/pages/account/usage/components/UsageForm.tsx
Normal file
@ -0,0 +1,164 @@
|
||||
import { getTotalPoints } from '@/web/support/wallet/usage/api';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { addDays } from 'date-fns';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
LineChart,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
TooltipProps
|
||||
} from 'recharts';
|
||||
import { NameType, ValueType } from 'recharts/types/component/DefaultTooltipContent';
|
||||
import { UnitType } from '../index';
|
||||
|
||||
export type usageFormType = {
|
||||
date: string;
|
||||
totalPoints: number;
|
||||
};
|
||||
|
||||
const CustomTooltip = ({ active, payload }: TooltipProps<ValueType, NameType>) => {
|
||||
const data = payload?.[0]?.payload as usageFormType;
|
||||
const { t } = useTranslation();
|
||||
if (active && data) {
|
||||
return (
|
||||
<Box
|
||||
bg={'white'}
|
||||
p={3}
|
||||
borderRadius={'md'}
|
||||
border={'0.5px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
boxShadow={
|
||||
'0px 24px 48px -12px rgba(19, 51, 107, 0.20), 0px 0px 1px 0px rgba(19, 51, 107, 0.20)'
|
||||
}
|
||||
>
|
||||
<Box fontSize={'mini'} color={'myGray.600'} mb={3}>
|
||||
{data.date}
|
||||
</Box>
|
||||
<Box fontSize={'14px'} color={'myGray.900'} fontWeight={'medium'}>
|
||||
{`${formatNumber(data.totalPoints)} ${t('account_usage:points')}`}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const UsageForm = ({
|
||||
dateRange,
|
||||
selectTmbIds,
|
||||
usageSources,
|
||||
unit,
|
||||
Tabs,
|
||||
Selectors
|
||||
}: {
|
||||
dateRange: DateRangeType;
|
||||
selectTmbIds: string[];
|
||||
usageSources: UsageSourceEnum[];
|
||||
unit: UnitType;
|
||||
Tabs: React.ReactNode;
|
||||
Selectors: React.ReactNode;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
run: getTotalPointsData,
|
||||
data: totalPoints,
|
||||
loading: totalPointsLoading
|
||||
} = useRequest2(
|
||||
() =>
|
||||
getTotalPoints({
|
||||
dateStart: dateRange.from || new Date(),
|
||||
dateEnd: addDays(dateRange.to || new Date(), 1),
|
||||
teamMemberIds: selectTmbIds,
|
||||
sources: usageSources,
|
||||
unit
|
||||
}),
|
||||
{
|
||||
manual: true
|
||||
}
|
||||
);
|
||||
|
||||
const totalUsage = useMemo(() => {
|
||||
return totalPoints?.reduce((acc, curr) => acc + curr.totalPoints, 0);
|
||||
}, [totalPoints]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectTmbIds.length === 0 || usageSources.length === 0) return;
|
||||
getTotalPointsData();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [usageSources, selectTmbIds.length, dateRange, unit]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box>{Tabs}</Box>
|
||||
<Box>{Selectors}</Box>
|
||||
<MyBox isLoading={totalPointsLoading}>
|
||||
<Flex fontSize={'20px'} fontWeight={'medium'} my={6}>
|
||||
<Box color={'black'}>{`${t('account_usage:total_usage')}:`}</Box>
|
||||
<Box color={'primary.600'} ml={2}>
|
||||
{`${formatNumber(totalUsage || 0)} ${t('account_usage:points')}`}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mb={4} fontSize={'mini'} color={'myGray.500'} fontWeight={'medium'}>
|
||||
{t('account_usage:points')}
|
||||
</Flex>
|
||||
<ResponsiveContainer width="100%" height={424}>
|
||||
<LineChart data={totalPoints} margin={{ top: 10, right: 30, left: -12, bottom: 0 }}>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
padding={{ left: 40, right: 40 }}
|
||||
tickMargin={10}
|
||||
tickSize={0}
|
||||
tick={{ fontSize: '12px', color: '#667085', fontWeight: '500' }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickSize={0}
|
||||
tickMargin={12}
|
||||
tick={{ fontSize: '12px', color: '#667085', fontWeight: '500' }}
|
||||
/>
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
verticalCoordinatesGenerator={(props) => {
|
||||
const { width } = props;
|
||||
if (width < 500) {
|
||||
return [width * 0.2, width * 0.4, width * 0.6, width * 0.8];
|
||||
} else {
|
||||
return [
|
||||
width * 0.125,
|
||||
width * 0.25,
|
||||
width * 0.375,
|
||||
width * 0.5,
|
||||
width * 0.625,
|
||||
width * 0.75,
|
||||
width * 0.875
|
||||
];
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="totalPoints"
|
||||
stroke="#5E8FFF"
|
||||
strokeWidth={1.5}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</MyBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UsageForm);
|
||||
188
projects/app/src/pages/account/usage/components/UsageTable.tsx
Normal file
188
projects/app/src/pages/account/usage/components/UsageTable.tsx
Normal file
@ -0,0 +1,188 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr
|
||||
} from '@chakra-ui/react';
|
||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||
import { UsageSourceEnum, UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import dayjs from 'dayjs';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
||||
import { getUserUsages } from '@/web/support/wallet/usage/api';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
|
||||
import { addDays } from 'date-fns';
|
||||
import { ExportModalParams } from './ExportModal';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
const UsageDetail = dynamic(() => import('./UsageDetail'));
|
||||
const ExportModal = dynamic(() => import('./ExportModal'));
|
||||
|
||||
const UsageTableList = ({
|
||||
dateRange,
|
||||
selectTmbIds,
|
||||
usageSources,
|
||||
projectName,
|
||||
members,
|
||||
memberTotal,
|
||||
isSelectAllTmb,
|
||||
Tabs,
|
||||
Selectors
|
||||
}: {
|
||||
dateRange: DateRangeType;
|
||||
selectTmbIds: string[];
|
||||
usageSources: UsageSourceEnum[];
|
||||
projectName: string;
|
||||
members: TeamMemberItemType[];
|
||||
memberTotal: number;
|
||||
isSelectAllTmb: boolean;
|
||||
Tabs: React.ReactNode;
|
||||
Selectors: React.ReactNode;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystem();
|
||||
const { toast } = useToast();
|
||||
|
||||
const {
|
||||
data: usages,
|
||||
isLoading,
|
||||
Pagination,
|
||||
getData,
|
||||
total
|
||||
} = usePagination(getUserUsages, {
|
||||
pageSize: isPc ? 20 : 10,
|
||||
params: {
|
||||
dateStart: dateRange.from || new Date(),
|
||||
dateEnd: addDays(dateRange.to || new Date(), 1),
|
||||
sources: usageSources,
|
||||
teamMemberIds: selectTmbIds,
|
||||
isSelectAllTmb,
|
||||
projectName
|
||||
},
|
||||
defaultRequest: false
|
||||
});
|
||||
|
||||
const [usageDetail, setUsageDetail] = useState<UsageItemType>();
|
||||
const [currentParams, setCurrentParams] = useState<ExportModalParams | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if ((!isSelectAllTmb && selectTmbIds.length === 0) || usageSources.length === 0) return;
|
||||
getData(1);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [usageSources, selectTmbIds.length, projectName, dateRange, isSelectAllTmb]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box>{Tabs}</Box>
|
||||
<Flex flexDir={['column', 'row']} w={'100%'} alignItems={['flex-end', 'center']}>
|
||||
<Box>{Selectors}</Box>
|
||||
<Box flex={'1'} />
|
||||
<Button
|
||||
size={'md'}
|
||||
onClick={() => {
|
||||
if ((selectTmbIds.length === 0 && !isSelectAllTmb) || usageSources.length === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('account_usage:select_member_and_source_first')
|
||||
});
|
||||
}
|
||||
|
||||
setCurrentParams({
|
||||
dateStart: dateRange.from || new Date(),
|
||||
dateEnd: addDays(dateRange.to || new Date(), 1),
|
||||
sources: usageSources,
|
||||
teamMemberIds: selectTmbIds,
|
||||
teamMemberNames: members
|
||||
.filter((item) =>
|
||||
isSelectAllTmb
|
||||
? !selectTmbIds.includes(item.tmbId)
|
||||
: selectTmbIds.includes(item.tmbId)
|
||||
)
|
||||
.map((item) => item.memberName),
|
||||
isSelectAllTmb,
|
||||
projectName
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('common:Export')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<MyBox position={'relative'} overflowY={'auto'} mt={3} flex={1} isLoading={isLoading}>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('common:user.Time')}</Th>
|
||||
<Th>{t('account_usage:member')}</Th>
|
||||
<Th>{t('account_usage:user_type')}</Th>
|
||||
<Th>{t('account_usage:project_name')}</Th>
|
||||
<Th>{t('account_usage:total_points')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{usages.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
|
||||
<Td>
|
||||
<Flex alignItems={'center'} color={'myGray.500'}>
|
||||
<Avatar src={item.sourceMember.avatar} w={'20px'} mr={1} rounded={'full'} />
|
||||
{item.sourceMember.name}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{t(UsageSourceMap[item.source]?.label as any) || '-'}</Td>
|
||||
<Td>{t(item.appName as any) || '-'}</Td>
|
||||
<Td>{formatNumber(item.totalPoints) || 0}</Td>
|
||||
<Td>
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'whitePrimary'}
|
||||
onClick={() => setUsageDetail(item)}
|
||||
>
|
||||
{t('account_usage:details')}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{!isLoading && usages.length === 0 && (
|
||||
<EmptyTip text={t('account_usage:no_usage_records')}></EmptyTip>
|
||||
)}
|
||||
</TableContainer>
|
||||
</MyBox>
|
||||
<Flex mt={3} justifyContent={'center'}>
|
||||
<Pagination />
|
||||
</Flex>
|
||||
|
||||
{!!usageDetail && (
|
||||
<UsageDetail usage={usageDetail} onClose={() => setUsageDetail(undefined)} />
|
||||
)}
|
||||
|
||||
{!!currentParams && (
|
||||
<ExportModal
|
||||
onClose={() => setCurrentParams(null)}
|
||||
params={currentParams}
|
||||
memberTotal={isSelectAllTmb ? memberTotal - selectTmbIds.length : selectTmbIds.length}
|
||||
total={total}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsageTableList;
|
||||
@ -1,76 +1,66 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Flex,
|
||||
Box,
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
import { Flex, Box } from '@chakra-ui/react';
|
||||
import { UsageSourceEnum, UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { getUserUsages } from '@/web/support/wallet/usage/api';
|
||||
import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
|
||||
import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import dayjs from 'dayjs';
|
||||
import DateRangePicker, {
|
||||
type DateRangeType
|
||||
} from '@fastgpt/web/components/common/DateRangePicker';
|
||||
import { addDays } from 'date-fns';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { addDays, startOfMonth, startOfWeek } from 'date-fns';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import AccountContainer from '../components/AccountContainer';
|
||||
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import MultipleSelect from '@fastgpt/web/components/common/MySelect/MultipleSelect';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import UsageForm from './components/UsageForm';
|
||||
import UsageTableList from './components/UsageTable';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const UsageDetail = dynamic(() => import('./UsageDetail'));
|
||||
export enum UsageTabEnum {
|
||||
detail = 'detail',
|
||||
dashboard = 'dashboard'
|
||||
}
|
||||
|
||||
export type UnitType = 'day' | 'week' | 'month';
|
||||
|
||||
const UsageTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading } = useLoading();
|
||||
const { userInfo } = useUserStore();
|
||||
const router = useRouter();
|
||||
const { usageTab = UsageTabEnum.detail } = router.query as { usageTab: `${UsageTabEnum}` };
|
||||
const { data: members, ScrollData, total: memberTotal } = useScrollPagination(getTeamMembers, {});
|
||||
const [dateRange, setDateRange] = useState<DateRangeType>({
|
||||
from: addDays(new Date(), -7),
|
||||
to: new Date()
|
||||
});
|
||||
const [usageSource, setUsageSource] = useState<UsageSourceEnum | ''>('');
|
||||
const { isPc } = useSystem();
|
||||
const { userInfo } = useUserStore();
|
||||
const [usageDetail, setUsageDetail] = useState<UsageItemType>();
|
||||
const [selectTmbIds, setSelectTmbIds] = useState<string[]>([]);
|
||||
const [usageSources, setUsageSources] = useState<UsageSourceEnum[]>(
|
||||
Object.values(UsageSourceEnum)
|
||||
);
|
||||
const [isSelectAllTmb, setIsSelectAllTmb] = useState<boolean>(true);
|
||||
const [unit, setUnit] = useState<UnitType>('day');
|
||||
const [projectName, setProjectName] = useState<string>('');
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const sourceList = useMemo(
|
||||
() =>
|
||||
[
|
||||
{ label: t('account_usage:all'), value: '' },
|
||||
...Object.entries(UsageSourceMap).map(([key, value]) => ({
|
||||
label: t(value.label as any),
|
||||
value: key
|
||||
}))
|
||||
] as {
|
||||
label: never;
|
||||
value: UsageSourceEnum | '';
|
||||
}[],
|
||||
Object.entries(UsageSourceMap).map(([key, value]) => ({
|
||||
label: t(value.label as any),
|
||||
value: key
|
||||
})),
|
||||
[t]
|
||||
);
|
||||
|
||||
const [selectTmbId, setSelectTmbId] = useState(userInfo?.team?.tmbId);
|
||||
const { data: members, ScrollData } = useScrollPagination(getTeamMembers, {});
|
||||
const tmbList = useMemo(
|
||||
() =>
|
||||
members.map((item) => ({
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={item.avatar} w={'16px'} mr={1} />
|
||||
<Flex alignItems={'center'} color={'myGray.500'}>
|
||||
<Avatar src={item.avatar} w={'20px'} mr={1} rounded={'full'} />
|
||||
{item.memberName}
|
||||
</Flex>
|
||||
),
|
||||
@ -79,122 +69,198 @@ const UsageTable = () => {
|
||||
[members]
|
||||
);
|
||||
|
||||
const {
|
||||
data: usages,
|
||||
isLoading,
|
||||
Pagination,
|
||||
getData
|
||||
} = usePagination(getUserUsages, {
|
||||
pageSize: isPc ? 20 : 10,
|
||||
params: {
|
||||
dateStart: dateRange.from || new Date(),
|
||||
dateEnd: addDays(dateRange.to || new Date(), 1),
|
||||
source: usageSource as UsageSourceEnum,
|
||||
teamMemberId: selectTmbId ?? ''
|
||||
},
|
||||
defaultRequest: false
|
||||
});
|
||||
const Tabs = useMemo(
|
||||
() => (
|
||||
<FillRowTabs
|
||||
list={[
|
||||
{ label: t('account_usage:usage_detail'), value: 'detail' },
|
||||
{ label: t('account_usage:dashboard'), value: 'dashboard' }
|
||||
]}
|
||||
px={'1rem'}
|
||||
value={usageTab}
|
||||
onChange={(e) => {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
usageTab: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[router, t, usageTab]
|
||||
);
|
||||
|
||||
const Selectors = useMemo(
|
||||
() => (
|
||||
<Flex mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontSize={'mini'} fontWeight={'medium'} color={'myGray.900'} mr={4}>
|
||||
{t('common:user.Time')}
|
||||
</Box>
|
||||
<DateRangePicker
|
||||
defaultDate={dateRange}
|
||||
dateRange={dateRange}
|
||||
position="bottom"
|
||||
onChange={setDateRange}
|
||||
/>
|
||||
{usageTab === UsageTabEnum.dashboard && (
|
||||
<MySelect
|
||||
bg={'myGray.50'}
|
||||
minH={'32px'}
|
||||
height={'32px'}
|
||||
fontSize={'mini'}
|
||||
ml={1}
|
||||
list={[
|
||||
{ label: t('account_usage:every_day'), value: 'day' },
|
||||
{ label: t('account_usage:every_week'), value: 'week' },
|
||||
{ label: t('account_usage:every_month'), value: 'month' }
|
||||
]}
|
||||
value={unit}
|
||||
onchange={(val) => {
|
||||
if (!dateRange.from) return dateRange;
|
||||
|
||||
switch (val) {
|
||||
case 'week':
|
||||
setDateRange({
|
||||
from: startOfWeek(dateRange.from, { weekStartsOn: 1 }),
|
||||
to: dateRange.to
|
||||
});
|
||||
break;
|
||||
case 'month':
|
||||
setDateRange({
|
||||
from: startOfMonth(dateRange.from),
|
||||
to: dateRange.to
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
setUnit(val as 'day' | 'week' | 'month');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
{tmbList.length > 1 && userInfo?.team?.permission.hasManagePer && (
|
||||
<Flex alignItems={'center'} ml={6}>
|
||||
<Box fontSize={'mini'} fontWeight={'medium'} color={'myGray.900'} mr={4}>
|
||||
{t('account_usage:member')}
|
||||
</Box>
|
||||
<Box>
|
||||
<MultipleSelect<string>
|
||||
list={tmbList}
|
||||
value={selectTmbIds}
|
||||
onSelect={(val) => {
|
||||
setSelectTmbIds(val as string[]);
|
||||
}}
|
||||
itemWrap={false}
|
||||
height={'32px'}
|
||||
bg={'myGray.50'}
|
||||
w={'160px'}
|
||||
showCheckedIcon={false}
|
||||
ScrollData={ScrollData}
|
||||
isSelectAll={isSelectAllTmb}
|
||||
setIsSelectAll={setIsSelectAllTmb}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} ml={6}>
|
||||
<Box fontSize={'mini'} fontWeight={'medium'} color={'myGray.900'} mr={4}>
|
||||
{t('common:user.type')}
|
||||
</Box>
|
||||
<Box>
|
||||
<MultipleSelect<string>
|
||||
list={sourceList}
|
||||
value={usageSources}
|
||||
onSelect={(val) => setUsageSources(val as UsageSourceEnum[])}
|
||||
itemWrap={false}
|
||||
height={'32px'}
|
||||
bg={'myGray.50'}
|
||||
w={'160px'}
|
||||
showCheckedIcon={false}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
{usageTab === UsageTabEnum.detail && (
|
||||
<Flex alignItems={'center'} ml={6}>
|
||||
<Box
|
||||
fontSize={'mini'}
|
||||
fontWeight={'medium'}
|
||||
color={'myGray.900'}
|
||||
mr={4}
|
||||
whiteSpace={'nowrap'}
|
||||
>
|
||||
{t('common:user.Application Name')}
|
||||
</Box>
|
||||
<SearchInput
|
||||
placeholder={t('common:user.Application Name')}
|
||||
w={'160px'}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
),
|
||||
[
|
||||
dateRange,
|
||||
selectTmbIds,
|
||||
sourceList,
|
||||
t,
|
||||
tmbList,
|
||||
unit,
|
||||
usageSources,
|
||||
usageTab,
|
||||
inputValue,
|
||||
isSelectAllTmb
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getData(1);
|
||||
}, [usageSource, selectTmbId]);
|
||||
const timer = setTimeout(() => {
|
||||
setProjectName(inputValue);
|
||||
}, 300);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [inputValue]);
|
||||
|
||||
return (
|
||||
<AccountContainer>
|
||||
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
|
||||
<Flex
|
||||
flexDir={['column', 'row']}
|
||||
gap={2}
|
||||
w={'100%'}
|
||||
px={[3, 8]}
|
||||
alignItems={['flex-end', 'center']}
|
||||
>
|
||||
{tmbList.length > 1 && userInfo?.team?.permission.hasManagePer && (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box mr={2} flexShrink={0}>
|
||||
{t('account_usage:member')}
|
||||
</Box>
|
||||
<MySelect
|
||||
size={'sm'}
|
||||
minW={'100px'}
|
||||
ScrollData={ScrollData}
|
||||
list={tmbList}
|
||||
value={selectTmbId}
|
||||
onchange={setSelectTmbId}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Box flex={'1'} />
|
||||
<Flex alignItems={'center'} gap={3}>
|
||||
<DateRangePicker
|
||||
defaultDate={dateRange}
|
||||
position="bottom"
|
||||
onChange={setDateRange}
|
||||
onSuccess={() => getData(1)}
|
||||
/>
|
||||
<Pagination />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<TableContainer
|
||||
mt={2}
|
||||
px={[3, 8]}
|
||||
position={'relative'}
|
||||
flex={'1 0 0'}
|
||||
h={0}
|
||||
overflowY={'auto'}
|
||||
>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
{/* <Th>{t('account_usage:user.team.Member Name')}</Th> */}
|
||||
<Th>{t('account_usage:user_type')}</Th>
|
||||
<Th>
|
||||
<MySelect<UsageSourceEnum | ''>
|
||||
list={sourceList}
|
||||
value={usageSource}
|
||||
size={'sm'}
|
||||
onchange={(e) => {
|
||||
setUsageSource(e);
|
||||
}}
|
||||
w={'130px'}
|
||||
></MySelect>
|
||||
</Th>
|
||||
<Th>{t('account_usage:project_name')}</Th>
|
||||
<Th>{t('account_usage:total_points')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{usages.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
{/* <Td>{item.memberName}</Td> */}
|
||||
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
|
||||
<Td>{t(UsageSourceMap[item.source]?.label as any) || '-'}</Td>
|
||||
<Td>{t(item.appName as any) || '-'}</Td>
|
||||
<Td>{formatNumber(item.totalPoints) || 0}</Td>
|
||||
<Td>
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'whitePrimary'}
|
||||
onClick={() => setUsageDetail(item)}
|
||||
>
|
||||
{t('account_usage:details')}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{!isLoading && usages.length === 0 && (
|
||||
<EmptyTip text={t('account_usage:no_usage_records')}></EmptyTip>
|
||||
)}
|
||||
</TableContainer>
|
||||
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
{!!usageDetail && (
|
||||
<UsageDetail usage={usageDetail} onClose={() => setUsageDetail(undefined)} />
|
||||
<Box
|
||||
px={[3, 8]}
|
||||
pt={[0, 8]}
|
||||
pb={[0, 4]}
|
||||
h={'full'}
|
||||
overflow={'hidden'}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
>
|
||||
{usageTab === UsageTabEnum.detail && (
|
||||
<UsageTableList
|
||||
dateRange={dateRange}
|
||||
selectTmbIds={selectTmbIds}
|
||||
usageSources={usageSources}
|
||||
projectName={projectName}
|
||||
members={members}
|
||||
memberTotal={memberTotal}
|
||||
isSelectAllTmb={isSelectAllTmb}
|
||||
Tabs={Tabs}
|
||||
Selectors={Selectors}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
{usageTab === UsageTabEnum.dashboard && (
|
||||
<UsageForm
|
||||
dateRange={dateRange}
|
||||
selectTmbIds={selectTmbIds}
|
||||
usageSources={usageSources}
|
||||
unit={unit}
|
||||
Tabs={Tabs}
|
||||
Selectors={Selectors}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</AccountContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -359,6 +359,8 @@ const InputTypeConfig = ({
|
||||
<MultipleSelect<WorkflowIOValueTypeEnum>
|
||||
list={valueTypeSelectList}
|
||||
bg={'myGray.50'}
|
||||
minH={'40px'}
|
||||
py={2}
|
||||
value={selectValueTypeList || []}
|
||||
onSelect={(e) => {
|
||||
setValue('customInputConfig.selectValueTypeList', e);
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
import { POST } from '@/web/common/api/request';
|
||||
import { CreateTrainingUsageProps } from '@fastgpt/global/support/wallet/usage/api.d';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import {
|
||||
CreateTrainingUsageProps,
|
||||
GetTotalPointsProps,
|
||||
GetUsageProps
|
||||
} from '@fastgpt/global/support/wallet/usage/api.d';
|
||||
import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
|
||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
|
||||
export const getUserUsages = (
|
||||
data: PaginationProps<{
|
||||
dateStart: Date;
|
||||
dateEnd: Date;
|
||||
source?: UsageSourceEnum;
|
||||
teamMemberId: string;
|
||||
}>
|
||||
) => POST<PaginationResponse<UsageItemType>>(`/proApi/support/wallet/usage/getUsage`, data);
|
||||
export const getUserUsages = (data: PaginationProps<GetUsageProps>) =>
|
||||
POST<PaginationResponse<UsageItemType>>(`/proApi/support/wallet/usage/getUsage`, data);
|
||||
|
||||
export const getTotalPoints = (data: GetTotalPointsProps) =>
|
||||
POST<{ totalPoints: number; date: string }[]>(
|
||||
`/proApi/support/wallet/usage/getTotalPoints`,
|
||||
data
|
||||
);
|
||||
|
||||
export const postCreateTrainingUsage = (data: CreateTrainingUsageProps) =>
|
||||
POST<string>(`/support/wallet/usage/createTrainingUsage`, data);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user