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
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Live App](https://img.shields.io/badge/Live-cspinsights.app-6366f1)](https://cspinsights.app)

**A free, local-first billing reconciliation and pricing management toolkit for Microsoft CSP Direct (Tier 1) partners.**
**A free, local-first billing reconciliation, pricing management, and incentives analytics toolkit for Microsoft CSP Direct (Tier 1) partners.**

> Your data never leaves your browser. No backend, no account, no cloud uploads.

Expand All @@ -13,7 +13,7 @@

## Why CSP Insights?

Managing Microsoft CSP operations means wrestling with massive reconciliation CSVs and complex price lists every month. CSP Insights gives you immediate visual clarity and powerful tools — without backend servers or third-party data processing.
Managing Microsoft CSP operations means wrestling with massive reconciliation CSVs, complex price lists, and incentive earnings reports every month. CSP Insights gives you immediate visual clarity and powerful tools — without backend servers or third-party data processing.

## Features

Expand All @@ -37,6 +37,21 @@ Managing Microsoft CSP operations means wrestling with massive reconciliation CS
- **Version comparison** — load two price lists and spot price changes
- **Shopping cart & quotes** — build a cart and export professional PDF quotes

### Incentives & Earnings
- **Earnings Report** — import the Partner Center Incentives → Earnings → Export (Default) CSV
- Dashboard with total earnings, customer count, product count, and program breakdown
- Earnings over time (monthly bar chart)
- Top customers and top products by earning amount
- Drill-down: click a customer or product to open a detail view with per-lever and per-product charts
- Full records table with search filter and sortable columns
- **Payments Report** — import the Partner Center Incentives → Payments CSV
- Summary cards: total earned, total paid, total tax withheld, and payment count
- Payments by month chart (grouped earned vs. paid)
- Payment method breakdown with color-coded horizontal bar
- Sortable payments table with status, date, and amounts
- **Multi-file upload** — append multiple CSV exports to build a combined dataset
- **Session persistence** — earnings and payment data survive page refreshes via IndexedDB

### Data Management
- **Snapshots** — save and restore billing data and pricing catalogs
- **Backup & restore** — export/import all your data
Expand All @@ -50,6 +65,17 @@ Managing Microsoft CSP operations means wrestling with massive reconciliation CS
- **No telemetry** — no analytics, no tracking, no data collection
- Clearing your browser data removes everything

## Data Sources

| Report | Where to export in Partner Center |
|--------|----------------------------------|
| Billing reconciliation | Billing → Reconciliation → Download CSV |
| CSP price list | Pricing → License-based / Usage-based → Download |
| Incentives Earnings | Incentives → Earnings → Export → Default |
| Incentives Payments | Incentives → Payments → Export |

All files are processed entirely in your browser. Nothing is uploaded anywhere.

## Getting Started

### Use the hosted version
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "csp-insights",
"version": "1.0.0",
"description": "A local-first billing reconciliation and pricing management toolkit for Microsoft CSP Direct partners",
"version": "1.2.0",
"description": "A local-first billing reconciliation, pricing management, and incentives analytics toolkit for Microsoft CSP Direct partners",
"homepage": "https://cspinsights.app",
"license": "MIT",
"type": "module",
Expand Down
55 changes: 48 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import { HistoryModal } from './components/HistoryModal';
import { parseBillingCSVs } from './utils/csvParser';
import { useBillingStore } from './store/billingStore';
import { useSettingsStore } from './store/settingsStore';
import { useEarningsStore } from './store/earningsStore';
import { AzureAnalyzer } from './components/AzureAnalyzer';
import { NceAnalyzer } from './components/NceAnalyzer';
import { RenewalCalendar } from './components/RenewalCalendar';
import { PricingView } from './components/PricingView';
import { HomeDashboard } from './components/HomeDashboard';
import { Loader2, Settings, History, Sun, Moon, Search, LayoutGrid, BarChart3, Cloud, ShieldCheck, ExternalLink } from 'lucide-react';
import { EarningsView } from './components/EarningsView';
import { Loader2, Settings, History, Sun, Moon, Search, LayoutGrid, BarChart3, Cloud, ShieldCheck, ExternalLink, TrendingUp, CalendarDays } from 'lucide-react';
import { generateDemoData } from './utils/demoData';
import './App.css';

Expand All @@ -29,16 +32,18 @@ function App() {
} = useBillingStore();

const { loadSettings, theme, setTheme, companyDetails } = useSettingsStore();
const { loadFromDisk: loadEarningsFromDisk } = useEarningsStore();

// Navigation State
const [currentView, setCurrentView] = useState<'home' | 'dashboard' | 'settings' | 'azure' | 'nce' | 'pricing'>('home');
const [currentView, setCurrentView] = useState<'home' | 'dashboard' | 'settings' | 'azure' | 'nce' | 'renewals' | 'pricing' | 'incentives'>('home');
const [showHistory, setShowHistory] = useState(false);
const [isUploading, setIsUploading] = useState(false); // New state to control upload view overlay

// Load persisted data on mount
useEffect(() => {
loadFromDisk();
loadSettings();
loadEarningsFromDisk();
}, []);

const handleFileSelect = async (files: File[]) => {
Expand Down Expand Up @@ -111,7 +116,7 @@ function App() {
</div>

{/* Search Section */}
<div className="flex-center" style={{ flex: 1, justifyContent: 'center', padding: '0 2rem', opacity: (currentView === 'home' || currentView === 'pricing' || currentView === 'settings') ? 0 : 1, pointerEvents: (currentView === 'home' || currentView === 'pricing' || currentView === 'settings') ? 'none' : 'auto' }}>
<div className="flex-center" style={{ flex: 1, justifyContent: 'center', padding: '0 2rem', opacity: (currentView === 'home' || currentView === 'pricing' || currentView === 'settings' || currentView === 'incentives' || currentView === 'renewals') ? 0 : 1, pointerEvents: (currentView === 'home' || currentView === 'pricing' || currentView === 'settings' || currentView === 'incentives' || currentView === 'renewals') ? 'none' : 'auto' }}>
<div style={{ position: 'relative', width: '100%', maxWidth: '500px' }}>
<Search size={18} style={{ position: 'absolute', left: '1rem', top: '50%', transform: 'translateY(-50%)', color: 'var(--text-tertiary)' }} />
<input
Expand Down Expand Up @@ -161,7 +166,7 @@ function App() {
>
<LayoutGrid size={20} />
</button>
{(currentView === 'dashboard' || currentView === 'azure' || currentView === 'nce' || currentView === 'pricing') && (
{(currentView === 'dashboard' || currentView === 'azure' || currentView === 'nce' || currentView === 'renewals' || currentView === 'pricing' || currentView === 'incentives') && (
<button
onClick={() => setShowHistory(true)}
className="secondary-btn"
Expand Down Expand Up @@ -190,12 +195,36 @@ function App() {
)}

{currentView === 'home' && (
<HomeDashboard onNavigate={(view) => setCurrentView(view === 'billing' ? 'dashboard' : 'pricing')} />
<HomeDashboard onNavigate={(view) => {
if (view === 'billing') setCurrentView('dashboard');
else if (view === 'pricing') setCurrentView('pricing');
else if (view === 'incentives') setCurrentView('incentives');
}} />
)}

{currentView === 'pricing' && <PricingView />}

{(currentView === 'dashboard' || currentView === 'azure' || currentView === 'nce') && (
{currentView === 'incentives' && (
<div style={{ marginTop: '0' }}>
<div style={{ display: 'flex', gap: '1rem', marginBottom: '1.5rem', borderBottom: '1px solid var(--border-color)', paddingBottom: '0.5rem' }}>
<button
onClick={() => setCurrentView('incentives')}
style={{
display: 'flex', alignItems: 'center', gap: '0.5rem',
borderBottom: '2px solid #10B981',
color: 'var(--text-primary)',
background: 'none', border: 'none', padding: '0.5rem 1rem', cursor: 'pointer', fontWeight: 500,
borderBottomStyle: 'solid', borderBottomWidth: '2px', borderBottomColor: '#10B981'
}}
>
<TrendingUp size={18} /> Incentives &amp; Earnings
</button>
</div>
<EarningsView />
</div>
)}

{(currentView === 'dashboard' || currentView === 'azure' || currentView === 'nce' || currentView === 'renewals') && (
<>
{showDashboard && (
<div style={{ display: 'flex', gap: '1rem', marginBottom: '1.5rem', borderBottom: '1px solid var(--border-color)', paddingBottom: '0.5rem' }}>
Expand Down Expand Up @@ -232,6 +261,17 @@ function App() {
>
<ShieldCheck size={18} /> NCE Insights
</button>
<button
onClick={() => setCurrentView('renewals')}
style={{
display: 'flex', alignItems: 'center', gap: '0.5rem',
borderBottom: currentView === 'renewals' ? '2px solid var(--accent-primary)' : '2px solid transparent',
color: currentView === 'renewals' ? 'var(--text-primary)' : 'var(--text-tertiary)',
background: 'none', border: 'none', padding: '0.5rem 1rem', cursor: 'pointer', fontWeight: 500
}}
>
<CalendarDays size={18} /> Renewal Calendar
</button>
</div>
)}

Expand Down Expand Up @@ -277,14 +317,15 @@ function App() {
{currentView === 'dashboard' && <Dashboard data={dashboardData} onReset={handleAddFile} onClearData={handleClearData} />}
{currentView === 'azure' && <AzureAnalyzer />}
{currentView === 'nce' && <NceAnalyzer />}
{currentView === 'renewals' && <RenewalCalendar />}
</>
)}
</>
)}
</main>

<footer style={{ textAlign: 'center', padding: '2rem 1rem 1rem', color: 'var(--text-tertiary)', fontSize: '0.8rem', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.5rem' }}>
<span>CSP Insights v1.0.0</span>
<span>CSP Insights v1.2.0</span>
<span style={{ opacity: 0.4 }}>|</span>
<a href="https://github.com/hardinxcore/cspinsights.app" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--text-tertiary)', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: '0.3rem' }}>
GitHub <ExternalLink size={12} />
Expand Down
Loading
Loading