Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"react": "18.3.1",
"react-device-detect": "2.2.3",
"react-dom": "18.3.1",
"react-router-dom": "7.1.3",
"react-router-dom": "^7.9.6",
"simplebar-react": "3.3.0",
"swr": "2.3.0",
"vite": "^7.2.4",
Expand All @@ -66,7 +66,7 @@
},
"devDependencies": {
"@eslint/compat": "1.2.5",
"@eslint/eslintrc": "3.2.0",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "9.19.0",
"@types/node": "22.13.5",
"eslint": "9.19.0",
Expand All @@ -78,5 +78,10 @@
"knip": "5.45.0",
"prettier": "3.4.2"
},
"resolutions": {
"glob": "^11.0.0",
"brace-expansion": "^2.0.1",
"js-yaml": "^4.1.1"
},
"packageManager": "yarn@4.9.4+sha512.7b1cb0b62abba6a537b3a2ce00811a843bea02bcf53138581a6ae5b1bf563f734872bd47de49ce32a9ca9dcaff995aa789577ffb16811da7c603dcf69e73750b"
}
43 changes: 25 additions & 18 deletions src/pages/dashboard/default.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const getDialingCode = (countryCode) => {
export default function DashboardDefault() {
const theme = useTheme();
const isDarkMode = theme.palette.mode === 'dark';
const [category, setCategory] = useState('signup');
const [category, setCategory] = useState('all');
const [granularity, setGranularity] = useState('day');
const [groupBy, setGroupBy] = useState('date');
const [countryCode, setCountryCode] = useState(null);
Expand Down Expand Up @@ -259,14 +259,13 @@ export default function DashboardDefault() {
};

setFiltersApplied(appliedFilters);
setPage(0);
};

const handleResetFilters = () => {
const resetStartDate = dayjs('2021-01-01');
const resetEndDate = dayjs();

setCategory('signup');
setCategory('all');
// setGranularity('month');
// setGroupBy('country');
setCountryCode(null);
Expand All @@ -276,7 +275,7 @@ export default function DashboardDefault() {
setShowCustomDatePickers(false);

setFiltersApplied({
category: 'signup',
category: 'all',
startDate: resetStartDate.format('YYYY-MM-DD'),
endDate: resetEndDate.format('YYYY-MM-DD')
// granularity: 'month',
Expand Down Expand Up @@ -427,8 +426,15 @@ export default function DashboardDefault() {
}
});

const effectiveCategory = filtersApplied.category || 'signup';
const countryCodes = effectiveCategory === 'signup' ? data.signup_countries || [] : data.retained_countries || [];
const effectiveCategory = filtersApplied.category || 'all';
let countryCodes = [];
if (effectiveCategory === 'all') {
const signupCountries = data.signup_countries || [];
const retainedCountries = data.retained_countries || [];
countryCodes = Array.from(new Set([...signupCountries, ...retainedCountries]));
} else {
countryCodes = effectiveCategory === 'signup' ? data.signup_countries || [] : data.retained_countries || [];
}
const countryOptions = countryCodes
.map((code) => {
const upperCode = typeof code === 'string' ? code.toUpperCase() : undefined;
Expand Down Expand Up @@ -538,25 +544,25 @@ export default function DashboardDefault() {
count={metrics.totalUsers === 0 ? '-' : metrics.totalUsers.toLocaleString()}
percentage={metrics.totalUsers === 0 ? null : metrics.percentages.totalUsers}
isLoss={!metrics.isHigher.totalUsers}
extra="Total retained users"
extra="Total current users"
/>
</Grid>
<Grid size={12}>
<AnalyticEcommerce
title="Retained Countries"
title="Current Countries"
count={metrics.totalRetainedCountries === 0 ? '-' : metrics.totalRetainedCountries.toLocaleString()}
percentage={metrics.totalRetainedCountries === 0 ? null : metrics.percentages.totalRetainedCountries}
isLoss={!metrics.isHigher.totalRetainedCountries}
extra="Countries with retained users"
extra="Countries with current users"
/>
</Grid>
<Grid size={12}>
<AnalyticEcommerce
title="Email Retained"
title="Email Current Users"
count={metrics.totalEmailRetained === 0 ? '-' : metrics.totalEmailRetained.toLocaleString()}
percentage={metrics.totalEmailRetained === 0 ? null : metrics.percentages.totalEmailRetained}
isLoss={!metrics.isHigher.totalEmailRetained}
extra="Retained from email signups"
extra="Current from email signups"
/>
</Grid>
</Grid>
Expand Down Expand Up @@ -610,8 +616,9 @@ export default function DashboardDefault() {
placeholder="Select Category"
value={category}
onChange={(value) => setCategory(value)}
style={{ width: '100%' }}
style={{ width: '120px' }}
options={[
{ value: 'all', label: 'All' },
{ value: 'signup', label: 'Sign-up Users' },
{ value: 'retained', label: 'Users' }
]}
Expand Down Expand Up @@ -682,11 +689,11 @@ export default function DashboardDefault() {
{/* row 2: Combined Chart and Map */}
<Grid size={12}>
<MainCard>
<Grid container spacing={3}>
<Grid size={{ xs: 12, md: 7, lg: 8.5 }}>
<Grid container spacing={1}>
<Grid size={{ xs: 12, md: 7, lg: 8 }}>
<CountryMap filters={filtersApplied} selectedCountry={selectedCountry} onCountrySelect={setSelectedCountry} />
</Grid>
<Grid size={{ xs: 12, md: 5, lg: 3.5 }}>
<Grid size={{ xs: 12, md: 5, lg: 4 }}>
<CountryTable filters={filtersApplied} onCountryClick={setSelectedCountry} selectedCountry={selectedCountry} />
</Grid>
</Grid>
Expand All @@ -696,11 +703,11 @@ export default function DashboardDefault() {
{/* row 3: Country Table and User Table */}
<Grid size={12} sx={{ mb: 4 }}>
<MainCard>
<Grid container spacing={3}>
<Grid size={{ xs: 12, md: 5, lg: 3.5 }}>
<Grid container spacing={1}>
<Grid size={{ xs: 12, md: 5, lg: 4 }}>
<UserTable filters={filtersApplied} />
</Grid>
<Grid size={{ xs: 12, md: 7, lg: 8.5 }}>
<Grid size={{ xs: 12, md: 7, lg: 8 }}>
<CombinedChartCard filters={filtersApplied} />
</Grid>
</Grid>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/publication/publications.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ export default function Publications() {
{tableData.map((row) => (
<TableRow key={row.id}>
<TableCell sx={{ width: '8%', fontSize: '0.80rem', padding: '8px' }}>{row.id}</TableCell>
<TableCell sx={{ width: '28%', fontSize: '0.80rem', padding: '8px', whiteSpace: 'nowrap' }}>
<TableCell sx={{ width: '8%', fontSize: '0.80rem', padding: '8px', whiteSpace: 'nowrap' }}>
{formatDateToGMTPlus1(row.date_created)}
</TableCell>
<TableCell
Expand All @@ -862,7 +862,7 @@ export default function Publications() {
}
}}
sx={{
width: '20%',
width: '28%',
fontSize: '0.85rem',
padding: '8px',
cursor: row.country_code && row.country_code !== 'UNKNOWN' ? 'pointer' : 'default',
Expand Down
161 changes: 120 additions & 41 deletions src/sections/dashboard/default/CountryTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,23 @@ import enLocale from 'i18n-iso-countries/langs/en.json';

// components
import Loader from 'components/Loader';
import { fontSize } from '@mui/system';

countries.registerLocale(enLocale);

function CountryTableHead({ order, orderBy, userLabel }) {
const headCells = useMemo(
() => [
{ id: 'date', align: 'left', label: 'Country' },
{ id: 'users', align: 'right', label: userLabel }
],
[userLabel]
);
function CountryTableHead({ order, orderBy, category }) {
const headCells =
category === 'all'
? [
{ id: 'country', align: 'left', label: 'Country' },
{ id: 'signupUsers', align: 'right', label: 'Sign-up Users' },
{ id: 'currentUsers', align: 'right', label: 'Current Users' }
]
: [
{ id: 'country', align: 'left', label: 'Country' },
{ id: 'users', align: 'right', label: category === 'signup' ? 'Signed Up Users' : 'Active Users' }
];

return (
<TableHead sx={{ backgroundColor: 'background.default', position: 'sticky', top: 0, zIndex: 1 }}>
<TableRow>
Expand All @@ -48,7 +54,7 @@ function CountryTableHead({ order, orderBy, userLabel }) {
CountryTableHead.propTypes = {
order: PropTypes.any,
orderBy: PropTypes.string,
userLabel: PropTypes.string.isRequired
category: PropTypes.string.isRequired
};

export default function CountryTable({ filters, onCountryClick, selectedCountry }) {
Expand All @@ -65,9 +71,7 @@ export default function CountryTable({ filters, onCountryClick, selectedCountry
const effectiveStartDate = filters?.startDate || '2020-01-10';
const effectiveEndDate = filters?.endDate || today.toISOString().split('T')[0];
const effectiveGranularity = filters?.granularity || 'day';
const effectiveCategory = filters?.category || 'signup';

const userLabel = effectiveCategory === 'signup' ? 'Signed Up Users' : 'Active Users';
const effectiveCategory = filters?.category || 'all';

const countryCodeToEmojiFlag = (code) => {
if (!code) return '';
Expand All @@ -79,32 +83,100 @@ export default function CountryTable({ filters, onCountryClick, selectedCountry
setLoading(true);
try {
const countryParam = filters?.countryCode ? `&country_code=${filters.countryCode}` : '';
const response = await axios.get(
`${import.meta.env.VITE_APP_TELEMETRY_API}${effectiveCategory}?start_date=${effectiveStartDate}&end_date=${effectiveEndDate}&granularity=${effectiveGranularity}&group_by=country&page=${page + 1}&page_size=${rowsPerPage}${countryParam}`
);

const categoryKey = effectiveCategory.includes('retained') ? 'retained' : 'signup';
const countryStats = response.data[categoryKey]?.data || [];

const formatted = countryStats.map((item) => {
const rawCode = item?.country_code;
const code = typeof rawCode === 'string' ? rawCode.toUpperCase() : undefined;
const isValidCode = code && countries.isValid(code, 'en');
const name = isValidCode ? countries.getName(code, 'en') : 'Unknown';
const flag = isValidCode ? countryCodeToEmojiFlag(code) : '';

const count = item.signup_users ?? item.retained_users ?? 0;

return {
country: name || code || 'Unknown',
countryCode: code,
users: count,
flag: flag || ''
};
});

setCountryData(formatted);
setTotalRows(response.data[categoryKey]?.pagination?.total_records || 0);

if (effectiveCategory === 'all') {
const [signupResponse, retainedResponse] = await Promise.all([
axios.get(
`${import.meta.env.VITE_APP_TELEMETRY_API}signup?start_date=${effectiveStartDate}&end_date=${effectiveEndDate}&granularity=${effectiveGranularity}&group_by=country&page=${page + 1}&page_size=${rowsPerPage}${countryParam}`
),
axios.get(
`${import.meta.env.VITE_APP_TELEMETRY_API}retained?start_date=${effectiveStartDate}&end_date=${effectiveEndDate}&granularity=${effectiveGranularity}&group_by=country&page=${page + 1}&page_size=${rowsPerPage}${countryParam}`
)
]);

const signupStats = signupResponse.data.signup?.data || [];
const retainedStats = retainedResponse.data.retained?.data || [];

const countryMap = new Map();

signupStats.forEach((item) => {
const rawCode = item?.country_code;
const code = typeof rawCode === 'string' ? rawCode.toUpperCase() : undefined;
if (code) {
countryMap.set(code, {
country_code: code,
signup_users: item.signup_users ?? 0,
retained_users: 0
});
}
});

retainedStats.forEach((item) => {
const rawCode = item?.country_code;
const code = typeof rawCode === 'string' ? rawCode.toUpperCase() : undefined;
if (code) {
const existing = countryMap.get(code) || { country_code: code, signup_users: 0, retained_users: 0 };
existing.retained_users = item.retained_users ?? 0;
countryMap.set(code, existing);
}
});

const formatted = Array.from(countryMap.values()).map((item) => {
const code = item.country_code;
const isValidCode = code && countries.isValid(code, 'en');
const name = isValidCode ? countries.getName(code, 'en') : 'Unknown';
const flag = isValidCode ? countryCodeToEmojiFlag(code) : '';

return {
country: name || code || 'Unknown',
countryCode: code,
signupUsers: item.signup_users,
retainedUsers: item.retained_users,
flag: flag || ''
};
});

formatted.sort((a, b) => {
const totalA = a.signupUsers + a.retainedUsers;
const totalB = b.signupUsers + b.retainedUsers;
return totalB - totalA;
});

setCountryData(formatted);
setTotalRows(
Math.max(
signupResponse.data.signup?.pagination?.total_records || 0,
retainedResponse.data.retained?.pagination?.total_records || 0
)
);
} else {
const response = await axios.get(
`${import.meta.env.VITE_APP_TELEMETRY_API}${effectiveCategory}?start_date=${effectiveStartDate}&end_date=${effectiveEndDate}&granularity=${effectiveGranularity}&group_by=country&page=${page + 1}&page_size=${rowsPerPage}${countryParam}`
);

const categoryKey = effectiveCategory.includes('retained') ? 'retained' : 'signup';
const countryStats = response.data[categoryKey]?.data || [];

const formatted = countryStats.map((item) => {
const rawCode = item?.country_code;
const code = typeof rawCode === 'string' ? rawCode.toUpperCase() : undefined;
const isValidCode = code && countries.isValid(code, 'en');
const name = isValidCode ? countries.getName(code, 'en') : 'Unknown';
const flag = isValidCode ? countryCodeToEmojiFlag(code) : '';

const count = item.signup_users ?? item.retained_users ?? 0;

return {
country: name || code || 'Unknown',
countryCode: code,
users: count,
flag: flag || ''
};
});

setCountryData(formatted);
setTotalRows(response.data[categoryKey]?.pagination?.total_records || 0);
}
setError('');
} catch (err) {
console.error('Error fetching country data:', err);
Expand Down Expand Up @@ -141,7 +213,7 @@ export default function CountryTable({ filters, onCountryClick, selectedCountry

{!loading && !error && (
<Table>
<CountryTableHead order={order} orderBy={orderBy} userLabel={userLabel} />
<CountryTableHead order={order} orderBy={orderBy} category={effectiveCategory} />
<TableBody>
{countryData.length > 0 ? (
countryData.map((row, index) => (
Expand All @@ -161,12 +233,19 @@ export default function CountryTable({ filters, onCountryClick, selectedCountry
<span style={{ marginRight: 8 }}>{row.flag}</span>
{row.country}
</TableCell>
<TableCell align="right">{row.users}</TableCell>
{effectiveCategory === 'all' ? (
<>
<TableCell align="right">{row.signupUsers}</TableCell>
<TableCell align="right">{row.retainedUsers}</TableCell>
</>
) : (
<TableCell align="right">{row.users}</TableCell>
)}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={2} align="center">
<TableCell colSpan={effectiveCategory === 'all' ? 3 : 2} align="center">
No data available
</TableCell>
</TableRow>
Expand Down
Loading
Loading