refactor: enhance type safety by replacing any types with stricter types across the codebase, improving error handling and reducing potential bugs
This commit is contained in:
@@ -20,7 +20,7 @@ export default function Locations({ countries, cities }: LocationProps) {
|
||||
if (!countryCode || countryCode === 'Unknown') return null
|
||||
// * The API returns 2-letter country codes (e.g. US, DE)
|
||||
// * We cast it to the flag component name
|
||||
const FlagComponent = (Flags as any)[countryCode]
|
||||
const FlagComponent = (Flags as Record<string, React.ComponentType<{ className?: string }>>)[countryCode]
|
||||
return FlagComponent ? <FlagComponent className="w-5 h-5 rounded-sm shadow-sm" /> : null
|
||||
}
|
||||
|
||||
|
||||
@@ -67,9 +67,9 @@ export default function ExportModal({ isOpen, onClose, data, stats, topPages, to
|
||||
|
||||
// Prepare data
|
||||
const exportData = data.map((item) => {
|
||||
const filteredItem: Partial<DailyStat> = {}
|
||||
const filteredItem: Record<string, string | number> = {}
|
||||
fields.forEach((field) => {
|
||||
(filteredItem as any)[field] = item[field]
|
||||
filteredItem[field] = item[field]
|
||||
})
|
||||
return filteredItem
|
||||
})
|
||||
@@ -212,7 +212,7 @@ export default function ExportModal({ isOpen, onClose, data, stats, topPages, to
|
||||
autoTable(doc, {
|
||||
startY: startY,
|
||||
head: [fields.map(f => f.charAt(0).toUpperCase() + f.slice(1).replace('_', ' '))],
|
||||
body: tableData as any[][],
|
||||
body: tableData as (string | number)[][],
|
||||
styles: {
|
||||
font: 'helvetica',
|
||||
fontSize: 9,
|
||||
@@ -249,7 +249,7 @@ export default function ExportModal({ isOpen, onClose, data, stats, topPages, to
|
||||
}
|
||||
})
|
||||
|
||||
let finalY = (doc as any).lastAutoTable.finalY + 10
|
||||
let finalY = doc.lastAutoTable.finalY + 10
|
||||
|
||||
// Top Pages Table
|
||||
if (topPages && topPages.length > 0) {
|
||||
@@ -276,7 +276,7 @@ export default function ExportModal({ isOpen, onClose, data, stats, topPages, to
|
||||
alternateRowStyles: { fillColor: [255, 250, 245] },
|
||||
})
|
||||
|
||||
finalY = (doc as any).lastAutoTable.finalY + 10
|
||||
finalY = doc.lastAutoTable.finalY + 10
|
||||
}
|
||||
|
||||
// Top Referrers Table
|
||||
@@ -305,7 +305,7 @@ export default function ExportModal({ isOpen, onClose, data, stats, topPages, to
|
||||
alternateRowStyles: { fillColor: [255, 250, 245] },
|
||||
})
|
||||
|
||||
finalY = (doc as any).lastAutoTable.finalY + 10
|
||||
finalY = doc.lastAutoTable.finalY + 10
|
||||
}
|
||||
|
||||
// Campaigns Table
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { formatNumber } from '@ciphera-net/ui'
|
||||
import * as Flags from 'country-flag-icons/react/3x2'
|
||||
// @ts-ignore
|
||||
import iso3166 from 'iso-3166-2'
|
||||
import WorldMap from './WorldMap'
|
||||
import { Modal, GlobeIcon } from '@ciphera-net/ui'
|
||||
@@ -28,7 +27,8 @@ const LIMIT = 7
|
||||
export default function Locations({ countries, cities, regions, geoDataLevel = 'full', siteId, dateRange }: LocationProps) {
|
||||
const [activeTab, setActiveTab] = useState<Tab>('map')
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [fullData, setFullData] = useState<any[]>([])
|
||||
type LocationItem = { country?: string; city?: string; region?: string; pageviews: number }
|
||||
const [fullData, setFullData] = useState<LocationItem[]>([])
|
||||
const [isLoadingFull, setIsLoadingFull] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -36,7 +36,7 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
||||
const fetchData = async () => {
|
||||
setIsLoadingFull(true)
|
||||
try {
|
||||
let data: any[] = []
|
||||
let data: LocationItem[] = []
|
||||
if (activeTab === 'countries') {
|
||||
data = await getCountries(siteId, dateRange.start, dateRange.end, 250)
|
||||
} else if (activeTab === 'regions') {
|
||||
@@ -73,7 +73,7 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
||||
return <GlobeIcon className="w-5 h-5 text-neutral-500 dark:text-neutral-400" />
|
||||
}
|
||||
|
||||
const FlagComponent = (Flags as any)[countryCode]
|
||||
const FlagComponent = (Flags as Record<string, React.ComponentType<{ className?: string }>>)[countryCode]
|
||||
return FlagComponent ? <FlagComponent className="w-5 h-5 rounded-sm shadow-sm" /> : null
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
||||
}
|
||||
|
||||
// Filter out "Unknown" entries that result from disabled collection
|
||||
const filterUnknown = (data: any[]) => {
|
||||
const filterUnknown = (data: LocationItem[]) => {
|
||||
return data.filter(item => {
|
||||
if (activeTab === 'countries') return item.country && item.country !== 'Unknown' && item.country !== ''
|
||||
if (activeTab === 'regions') return item.region && item.region !== 'Unknown' && item.region !== ''
|
||||
@@ -172,7 +172,7 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
||||
const hasData = activeTab === 'map'
|
||||
? (countries && filterUnknown(countries).length > 0)
|
||||
: (data && data.length > 0)
|
||||
const displayedData = (activeTab !== 'map' && hasData) ? (data as any[]).slice(0, LIMIT) : []
|
||||
const displayedData = (activeTab !== 'map' && hasData) ? data.slice(0, LIMIT) : []
|
||||
const emptySlots = Math.max(0, LIMIT - displayedData.length)
|
||||
const showViewAll = activeTab !== 'map' && hasData && data.length > LIMIT
|
||||
|
||||
@@ -228,7 +228,7 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
||||
<p className="text-neutral-500 dark:text-neutral-400 text-sm">{getDisabledMessage()}</p>
|
||||
</div>
|
||||
) : activeTab === 'map' ? (
|
||||
hasData ? <WorldMap data={filterUnknown(countries)} /> : (
|
||||
hasData ? <WorldMap data={filterUnknown(countries) as { country: string; pageviews: number }[]} /> : (
|
||||
<div className="h-full flex flex-col items-center justify-center text-center px-6 py-8 gap-3">
|
||||
<div className="rounded-full bg-neutral-100 dark:bg-neutral-800 p-4">
|
||||
<GlobeIcon className="w-8 h-8 text-neutral-500 dark:text-neutral-400" />
|
||||
@@ -247,13 +247,13 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
||||
{displayedData.map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between h-9 group hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-lg px-2 -mx-2 transition-colors">
|
||||
<div className="flex-1 truncate text-neutral-900 dark:text-white flex items-center gap-3">
|
||||
{activeTab === 'countries' && <span className="shrink-0">{getFlagComponent(item.country)}</span>}
|
||||
{activeTab !== 'countries' && <span className="shrink-0">{getFlagComponent(item.country)}</span>}
|
||||
{activeTab === 'countries' && <span className="shrink-0">{getFlagComponent(item.country ?? '')}</span>}
|
||||
{activeTab !== 'countries' && <span className="shrink-0">{getFlagComponent(item.country ?? '')}</span>}
|
||||
|
||||
<span className="truncate">
|
||||
{activeTab === 'countries' ? getCountryName(item.country) :
|
||||
activeTab === 'regions' ? getRegionName(item.region, item.country) :
|
||||
getCityName(item.city)}
|
||||
{activeTab === 'countries' ? getCountryName(item.country ?? '') :
|
||||
activeTab === 'regions' ? getRegionName(item.region ?? '', item.country ?? '') :
|
||||
getCityName(item.city ?? '')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-neutral-600 dark:text-neutral-400 ml-4">
|
||||
@@ -293,14 +293,14 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
||||
<ListSkeleton rows={10} />
|
||||
</div>
|
||||
) : (
|
||||
(fullData.length > 0 ? fullData : data as any[]).map((item, index) => (
|
||||
(fullData.length > 0 ? fullData : data).map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between py-2 group hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-lg px-2 -mx-2 transition-colors">
|
||||
<div className="flex-1 truncate text-neutral-900 dark:text-white flex items-center gap-3">
|
||||
<span className="shrink-0">{getFlagComponent(item.country)}</span>
|
||||
<span className="shrink-0">{getFlagComponent(item.country ?? '')}</span>
|
||||
<span className="truncate">
|
||||
{activeTab === 'countries' ? getCountryName(item.country) :
|
||||
activeTab === 'regions' ? getRegionName(item.region, item.country) :
|
||||
getCityName(item.city)}
|
||||
{activeTab === 'countries' ? getCountryName(item.country ?? '') :
|
||||
activeTab === 'regions' ? getRegionName(item.region ?? '', item.country ?? '') :
|
||||
getCityName(item.city ?? '')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-neutral-600 dark:text-neutral-400 ml-4">
|
||||
|
||||
@@ -26,7 +26,8 @@ const LIMIT = 7
|
||||
export default function TechSpecs({ browsers, os, devices, screenResolutions, collectDeviceInfo = true, collectScreenResolution = true, siteId, dateRange }: TechSpecsProps) {
|
||||
const [activeTab, setActiveTab] = useState<Tab>('browsers')
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [fullData, setFullData] = useState<any[]>([])
|
||||
type TechItem = { name: string; pageviews: number; icon: React.ReactNode }
|
||||
const [fullData, setFullData] = useState<TechItem[]>([])
|
||||
const [isLoadingFull, setIsLoadingFull] = useState(false)
|
||||
|
||||
// Filter out "Unknown" entries that result from disabled collection
|
||||
@@ -39,7 +40,7 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co
|
||||
const fetchData = async () => {
|
||||
setIsLoadingFull(true)
|
||||
try {
|
||||
let data: any[] = []
|
||||
let data: TechItem[] = []
|
||||
if (activeTab === 'browsers') {
|
||||
const res = await getBrowsers(siteId, dateRange.start, dateRange.end, 100)
|
||||
data = res.map(b => ({ name: b.browser, pageviews: b.pageviews, icon: getBrowserIcon(b.browser) }))
|
||||
|
||||
Reference in New Issue
Block a user