Initial build: SimpleScan Solo v0.1.0 - Freelancer receipt capture Chrome extension

This commit is contained in:
Bun Bun
2026-06-16 18:27:31 +00:00
commit df4d26c049
33 changed files with 3962 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
export const TAX_CATEGORIES = [
{
id: 'equipment',
label: 'Equipment',
description: 'Computers, cameras, tools, hardware',
icon: '💻',
},
{
id: 'home_office',
label: 'Home Office',
description: 'Desk, chair, lighting, office supplies',
icon: '🏠',
},
{
id: 'software',
label: 'Software',
description: 'Apps, subscriptions, SaaS tools',
icon: '⚙️',
},
{
id: 'internet_phone',
label: 'Internet & Phone',
description: 'ISP, cell plan, domain hosting',
icon: '📡',
},
{
id: 'travel',
label: 'Travel',
description: 'Flights, hotels, rideshare, mileage',
icon: '✈️',
},
{
id: 'meals',
label: 'Meals',
description: 'Business meals (50% deductible)',
icon: '🍽️',
},
{
id: 'professional_services',
label: 'Professional Services',
description: 'Lawyer, accountant, contractor payments',
icon: '📋',
},
{
id: 'marketing',
label: 'Marketing',
description: 'Ads, design, copywriting, PR',
icon: '📢',
},
{
id: 'education',
label: 'Education',
description: 'Courses, books, conferences',
icon: '📚',
},
{
id: 'other',
label: 'Other',
description: 'Miscellaneous business expenses',
icon: '📦',
},
];
export function getCategoryLabel(id) {
return TAX_CATEGORIES.find((c) => c.id === id)?.label || 'Other';
}
export function getCategoryIcon(id) {
return TAX_CATEGORIES.find((c) => c.id === id)?.icon || '📦';
}
+32
View File
@@ -0,0 +1,32 @@
import { getCategoryLabel } from './categories.js';
export function exportToCsv(receipts) {
const headers = ['Date', 'Vendor', 'Title', 'Category', 'Amount', 'Currency', 'Notes', 'Source'];
const rows = receipts.map((r) => [
r.date,
r.vendor,
r.title,
getCategoryLabel(r.category),
r.amount.toFixed(2),
r.currency,
r.notes,
r.source,
]);
const csv = [headers, ...rows]
.map((row) => row
.map((cell) => {
const str = String(cell).replace(/"/g, '""');
return `"${str}"`;
})
.join(','))
.join('\n');
return csv;
}
export function downloadBlob(content, filename, mimeType) {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
+33
View File
@@ -0,0 +1,33 @@
import { DEFAULT_SETTINGS } from '../types.js';
const RECEIPTS_KEY = 'simplescan_receipts';
const SETTINGS_KEY = 'simplescan_settings';
export async function getReceipts() {
const result = await chrome.storage.local.get(RECEIPTS_KEY);
return result[RECEIPTS_KEY] || [];
}
export async function saveReceipt(receipt) {
const receipts = await getReceipts();
const existingIndex = receipts.findIndex((r) => r.id === receipt.id);
if (existingIndex >= 0) {
receipts[existingIndex] = receipt;
}
else {
receipts.unshift(receipt);
}
await chrome.storage.local.set({ [RECEIPTS_KEY]: receipts });
}
export async function deleteReceipt(id) {
const receipts = await getReceipts();
const filtered = receipts.filter((r) => r.id !== id);
await chrome.storage.local.set({ [RECEIPTS_KEY]: filtered });
}
export async function getSettings() {
const result = await chrome.storage.local.get(SETTINGS_KEY);
return result[SETTINGS_KEY] || { ...DEFAULT_SETTINGS };
}
export async function saveSettings(settings) {
await chrome.storage.local.set({ [SETTINGS_KEY]: settings });
}
export async function clearAllReceipts() {
await chrome.storage.local.remove(RECEIPTS_KEY);
}