feat: integrate Glance Views (GV) from Traffic Weekly.xlsx

- types.ts: Added TrafficRecord interface and glanceViews field to CombinedKPIs
- dataProcessor.ts: Added processTrafficExcel parser for Traffic Weekly.xlsx
- dataProcessor.ts: Updated mergeSalesAndAdsData to accept and merge traffic data
- App.tsx: Added trafficData state and handleTrafficUpload handler
- FileUpload.tsx: Added Traffic file upload section for GV data
- AdsPerformance.tsx: Added GV and CVR(GV) summary cards
- AdsPerformance.tsx: Added GV column to product table
This commit is contained in:
Christian Vidal Wolf
2026-01-23 09:07:38 +01:00
parent 473174f7e6
commit affae9e994
6 changed files with 150 additions and 11 deletions
+8 -1
View File
@@ -77,6 +77,7 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters }) => {
target.impressions += item.impressions;
target.conversions += item.conversions;
target.salesOrganic += item.salesOrganic;
target.glanceViews = (target.glanceViews || 0) + (item.glanceViews || 0);
}
}
});
@@ -112,7 +113,8 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters }) => {
impressions: acc.impressions + curr.impressions,
clicks: acc.clicks + curr.clicks,
conversions: acc.conversions + curr.conversions,
}), { salesTotal: 0, salesAds: 0, salesOrganic: 0, cost: 0, impressions: 0, clicks: 0, conversions: 0 });
glanceViews: acc.glanceViews + (curr.glanceViews || 0),
}), { salesTotal: 0, salesAds: 0, salesOrganic: 0, cost: 0, impressions: 0, clicks: 0, conversions: 0, glanceViews: 0 });
const currTotals = sum(aggregatedByAsin);
const prevTotals = comparisonConfig ? sum(aggregatedByAsin.map(a => a.prevData).filter(Boolean)) : null;
@@ -127,6 +129,7 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters }) => {
ctr: t.impressions > 0 ? (t.clicks / t.impressions) * 100 : 0,
cpc: t.clicks > 0 ? t.cost / t.clicks : 0,
cvr: t.clicks > 0 ? (t.conversions / t.clicks) * 100 : 0,
cvrGv: t.glanceViews > 0 ? (t.conversions / t.glanceViews) * 100 : 0,
});
const currMetrics = calculateDerived(totals.curr);
@@ -196,6 +199,8 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters }) => {
<SummaryCard label="ACOS" value={currMetrics.acos} prevValue={prevMetrics?.acos} format="percent" inverse />
<SummaryCard label="ROAS" value={currMetrics.roas} prevValue={prevMetrics?.roas} format="number" decimals={2} color="text-blue-300" />
<SummaryCard label="TACOS" value={currMetrics.tacos} prevValue={prevMetrics?.tacos} format="percent" inverse />
<SummaryCard label="GV" value={totals.curr.glanceViews} prevValue={totals.prev?.glanceViews} format="number" color="text-teal-400" />
<SummaryCard label="CVR (GV)" value={currMetrics.cvrGv} prevValue={prevMetrics?.cvrGv} format="percent" color="text-teal-300" />
</div>
{/* Table Section */}
@@ -236,6 +241,7 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters }) => {
<SortableHeader label="ACOS" sortKey="acos" activeSort={sortConfig} onSort={handleSort} />
<SortableHeader label="ROAS" sortKey="roas" activeSort={sortConfig} onSort={handleSort} />
<SortableHeader label="TACOS" sortKey="tacos" activeSort={sortConfig} onSort={handleSort} />
<SortableHeader label="GV" sortKey="glanceViews" activeSort={sortConfig} onSort={handleSort} />
</tr>
</thead>
<tbody className="divide-y divide-white/[0.03]">
@@ -261,6 +267,7 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters }) => {
<TableCell value={row.acos} prevValue={row.prevData?.acos} format="percent" inverse />
<TableCell value={row.roas} prevValue={row.prevData?.roas} format="number" highlight />
<TableCell value={row.tacos} prevValue={row.prevData?.tacos} format="percent" inverse />
<TableCell value={row.glanceViews || 0} prevValue={row.prevData?.glanceViews} format="number" />
</tr>
))}
</tbody>
+34 -1
View File
@@ -5,7 +5,8 @@ import { UploadIcon, MegaphoneIcon } from './Icons';
interface FileUploadProps {
onSalesUpload: (file: File) => void;
onAdsUpload: (file: File) => void;
onUrlSubmit: () => void; // Changed: no longer takes URL parameter
onTrafficUpload?: (file: File) => void;
onUrlSubmit: () => void;
isLoading: boolean;
activeUrl?: string | null;
onDisconnect?: () => void;
@@ -15,6 +16,7 @@ interface FileUploadProps {
const FileUpload: React.FC<FileUploadProps> = ({
onSalesUpload,
onAdsUpload,
onTrafficUpload,
onUrlSubmit,
isLoading,
activeUrl,
@@ -35,6 +37,12 @@ const FileUpload: React.FC<FileUploadProps> = ({
}
};
const handleTrafficChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0 && onTrafficUpload) {
onTrafficUpload(e.target.files[0]);
}
};
const handleUrlSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (url.trim()) {
@@ -128,6 +136,31 @@ const FileUpload: React.FC<FileUploadProps> = ({
/>
</label>
</div>
{onTrafficUpload && (
<div className="rounded-xl border border-dashed border-slate-700 bg-slate-900/50 hover:bg-slate-900 transition-colors group h-full col-span-2">
<label htmlFor="traffic-upload" className="cursor-pointer flex flex-col items-center justify-center gap-3 p-6 h-full">
<div className="p-3 bg-teal-500/10 rounded-full text-teal-400 group-hover:scale-110 transition-transform">
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</div>
<div className="text-center">
<h3 className="font-semibold text-slate-200">Upload Traffic Excel</h3>
<p className="text-[10px] text-slate-500 mt-1">Glance Views (GV) Data</p>
</div>
<input
id="traffic-upload"
type="file"
accept=".xlsx,.xls"
onChange={handleTrafficChange}
disabled={isLoading}
className="hidden"
/>
</label>
</div>
)}
</div>
</div>
);