mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
feat(sales-stats): пресет «Этот месяц» (с 1-го числа) и он же по умолчанию
Статистика по умолчанию открывается за текущий месяц с 1-го числа до сегодня (month-to-date), а не за фиксированные 30 дней. Добавлена кнопка «Этот месяц» в селектор периода; util getMonthToDateRange/isMonthToDate.
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SALES_STATS } from '../../constants/salesStats';
|
||||
import { getMonthToDateRange, isMonthToDate } from '../../utils/period';
|
||||
|
||||
interface PeriodSelectorProps {
|
||||
value: { days?: number; startDate?: string; endDate?: string };
|
||||
@@ -24,46 +25,44 @@ export function PeriodSelector({ value, onChange }: PeriodSelectorProps) {
|
||||
onChange({ days });
|
||||
};
|
||||
|
||||
const handleCustomToggle = () => {
|
||||
setShowCustom((prev) => !prev);
|
||||
const handleThisMonth = () => {
|
||||
setShowCustom(false);
|
||||
onChange({ days: undefined, ...getMonthToDateRange() });
|
||||
};
|
||||
|
||||
const handleCustomToggle = () => setShowCustom((prev) => !prev);
|
||||
|
||||
const handleDateChange = (field: 'startDate' | 'endDate', dateStr: string) => {
|
||||
onChange({
|
||||
...value,
|
||||
days: undefined,
|
||||
[field]: dateStr,
|
||||
});
|
||||
onChange({ ...value, days: undefined, [field]: dateStr });
|
||||
};
|
||||
|
||||
const isPresetActive = (days: number) => !showCustom && value.days === days;
|
||||
const buttonClass = (active: boolean) =>
|
||||
`rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${
|
||||
active
|
||||
? 'bg-accent-500/20 text-accent-400'
|
||||
: 'bg-dark-800/50 text-dark-400 hover:bg-dark-700/50 hover:text-dark-300'
|
||||
}`;
|
||||
|
||||
const mtdActive = !showCustom && isMonthToDate(value);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<button type="button" onClick={handleThisMonth} className={buttonClass(mtdActive)}>
|
||||
{t('admin.salesStats.period.thisMonth')}
|
||||
</button>
|
||||
|
||||
{SALES_STATS.PERIOD_PRESETS.map((days) => (
|
||||
<button
|
||||
key={days}
|
||||
type="button"
|
||||
onClick={() => handlePreset(days)}
|
||||
className={`rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${
|
||||
isPresetActive(days)
|
||||
? 'bg-accent-500/20 text-accent-400'
|
||||
: 'bg-dark-800/50 text-dark-400 hover:bg-dark-700/50 hover:text-dark-300'
|
||||
}`}
|
||||
className={buttonClass(!showCustom && value.days === days)}
|
||||
>
|
||||
{presetLabels[days]}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCustomToggle}
|
||||
className={`rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${
|
||||
showCustom
|
||||
? 'bg-accent-500/20 text-accent-400'
|
||||
: 'bg-dark-800/50 text-dark-400 hover:bg-dark-700/50 hover:text-dark-300'
|
||||
}`}
|
||||
>
|
||||
<button type="button" onClick={handleCustomToggle} className={buttonClass(showCustom)}>
|
||||
{t('admin.salesStats.period.custom')}
|
||||
</button>
|
||||
|
||||
@@ -75,7 +74,7 @@ export function PeriodSelector({ value, onChange }: PeriodSelectorProps) {
|
||||
onChange={(e) => handleDateChange('startDate', e.target.value)}
|
||||
className="rounded-lg border border-dark-600 bg-dark-800 px-2 py-1 text-sm text-dark-200"
|
||||
/>
|
||||
<span className="text-dark-500">{'\u2014'}</span>
|
||||
<span className="text-dark-500">{'—'}</span>
|
||||
<input
|
||||
type="date"
|
||||
value={value.endDate || ''}
|
||||
|
||||
@@ -1282,6 +1282,7 @@
|
||||
"title": "Sales Statistics",
|
||||
"subtitle": "Revenue, trials, subscriptions, and renewals",
|
||||
"period": {
|
||||
"thisMonth": "This month",
|
||||
"week": "7 days",
|
||||
"month": "30 days",
|
||||
"quarter": "90 days",
|
||||
|
||||
@@ -1155,6 +1155,7 @@
|
||||
"title": "آمار فروش",
|
||||
"subtitle": "درآمد، آزمایشی، اشتراک و تمدید",
|
||||
"period": {
|
||||
"thisMonth": "این ماه",
|
||||
"week": "۷ روز",
|
||||
"month": "۳۰ روز",
|
||||
"quarter": "۹۰ روز",
|
||||
|
||||
@@ -1304,6 +1304,7 @@
|
||||
"title": "Статистика продаж",
|
||||
"subtitle": "Доход, триалы, подписки и продления",
|
||||
"period": {
|
||||
"thisMonth": "Этот месяц",
|
||||
"week": "7 дней",
|
||||
"month": "30 дней",
|
||||
"quarter": "90 дней",
|
||||
|
||||
@@ -1155,6 +1155,7 @@
|
||||
"title": "销售统计",
|
||||
"subtitle": "收入、试用、订阅和续订",
|
||||
"period": {
|
||||
"thisMonth": "本月",
|
||||
"week": "7天",
|
||||
"month": "30天",
|
||||
"quarter": "90天",
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { SalesStatsParams } from '../api/adminSalesStats';
|
||||
import { salesStatsApi } from '../api/adminSalesStats';
|
||||
import { SALES_STATS } from '../constants/salesStats';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { getMonthToDateRange } from '../utils/period';
|
||||
import { AdminBackButton } from '../components/admin/AdminBackButton';
|
||||
import {
|
||||
BanknotesIcon,
|
||||
@@ -72,7 +73,7 @@ export default function AdminSalesStats() {
|
||||
days?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}>({ days: SALES_STATS.DEFAULT_PERIOD });
|
||||
}>(() => getMonthToDateRange());
|
||||
|
||||
const params: SalesStatsParams = useMemo(
|
||||
() => ({
|
||||
|
||||
22
src/utils/period.ts
Normal file
22
src/utils/period.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/** First day of the current month → today, as local YYYY-MM-DD strings.
|
||||
* Used as the default sales-stats window ("this month so far"). */
|
||||
export function getMonthToDateRange(): { startDate: string; endDate: string } {
|
||||
const now = new Date();
|
||||
const toLocalISODate = (d: Date) =>
|
||||
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
||||
return {
|
||||
startDate: toLocalISODate(new Date(now.getFullYear(), now.getMonth(), 1)),
|
||||
endDate: toLocalISODate(now),
|
||||
};
|
||||
}
|
||||
|
||||
/** True when the period is exactly "from the 1st of the current month to today". */
|
||||
export function isMonthToDate(period: {
|
||||
days?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): boolean {
|
||||
if (period.days !== undefined) return false;
|
||||
const mtd = getMonthToDateRange();
|
||||
return period.startDate === mtd.startDate && period.endDate === mtd.endDate;
|
||||
}
|
||||
Reference in New Issue
Block a user