diff --git a/resources/js/components/Display/InlineForm.tsx b/resources/js/components/Display/InlineForm.tsx
index 7dd4ca8..1254e81 100644
--- a/resources/js/components/Display/InlineForm.tsx
+++ b/resources/js/components/Display/InlineForm.tsx
@@ -1,11 +1,12 @@
+import AddEntryForm from '@/components/Transactions/AddEntryForm';
import AddMilestoneForm from '@/components/Milestones/AddMilestoneForm';
-import AddPurchaseForm from '@/components/Transactions/AddPurchaseForm';
import UpdatePriceForm from '@/components/Pricing/UpdatePriceForm';
import { cn } from '@/lib/utils';
-import ComponentTitle from '@/components/ui/ComponentTitle';
interface InlineFormProps {
type: 'purchase' | 'milestone' | 'price' | null;
+ unit?: string;
+ priceTrackingEnabled?: boolean;
onClose: () => void;
onPurchaseSuccess?: () => void;
onMilestoneSuccess?: () => void;
@@ -15,31 +16,30 @@ interface InlineFormProps {
export default function InlineForm({
type,
+ unit = 'units',
+ priceTrackingEnabled = false,
onClose,
onPurchaseSuccess,
onMilestoneSuccess,
onPriceSuccess,
- className
+ className,
}: InlineFormProps) {
if (!type) return null;
- const title = type === 'purchase' ? 'ADD PURCHASE' : type === 'milestone' ? 'ADD MILESTONE' : 'UPDATE PRICE';
-
return (
- {/* Header */}
-
- {/* Form Content */}
{type === 'purchase' ? (
-
{
if (onPurchaseSuccess) onPurchaseSuccess();
onClose();
diff --git a/resources/js/components/Display/LedDisplay.tsx b/resources/js/components/Display/LedDisplay.tsx
index 77d24a8..4ca24a8 100644
--- a/resources/js/components/Display/LedDisplay.tsx
+++ b/resources/js/components/Display/LedDisplay.tsx
@@ -3,6 +3,7 @@ import { useEffect, useState } from 'react';
interface LedDisplayProps {
value: number;
+ unit?: string;
className?: string;
animate?: boolean;
onClick?: () => void;
@@ -10,6 +11,7 @@ interface LedDisplayProps {
export default function LedDisplay({
value,
+ unit,
className,
onClick
}: LedDisplayProps) {
@@ -55,6 +57,11 @@ export default function LedDisplay({
{formattedValue}
+ {unit && (
+
+ {unit}
+
+ )}
);
}
diff --git a/resources/js/components/Display/ProgressBar.tsx b/resources/js/components/Display/ProgressBar.tsx
index 4343cb5..ccf594d 100644
--- a/resources/js/components/Display/ProgressBar.tsx
+++ b/resources/js/components/Display/ProgressBar.tsx
@@ -7,28 +7,26 @@ interface Milestone {
}
interface ProgressBarProps {
- currentShares: number;
+ currentQuantity: number;
milestones: Milestone[];
selectedMilestoneIndex?: number;
className?: string;
onClick?: () => void;
}
-export default function ProgressBar({
- currentShares,
+export default function ProgressBar({
+ currentQuantity,
milestones,
selectedMilestoneIndex = 0,
className,
onClick
}: ProgressBarProps) {
- // Get the selected milestone for progress calculation
- const selectedMilestone = milestones.length > 0 && selectedMilestoneIndex < milestones.length
- ? milestones[selectedMilestoneIndex]
+ const selectedMilestone = milestones.length > 0 && selectedMilestoneIndex < milestones.length
+ ? milestones[selectedMilestoneIndex]
: null;
-
- // Calculate progress percentage
- const progressPercentage = selectedMilestone
- ? Math.min((currentShares / selectedMilestone.target) * 100, 100)
+
+ const progressPercentage = selectedMilestone
+ ? Math.min((currentQuantity / selectedMilestone.target) * 100, 100)
: 0;
return (
void;
@@ -32,6 +33,7 @@ interface StatsBoxProps {
export default function StatsBox({
stats,
+ unit = 'units',
milestones = [],
selectedMilestoneIndex = 0,
onMilestoneSelect,
@@ -107,7 +109,7 @@ export default function StatsBox({
}}
className="w-full text-left px-4 py-2 text-red-400 hover:bg-red-600/20 hover:text-red-300 transition-colors text-sm font-mono border-b border-red-500/20 last:border-b-0"
>
- ADD PURCHASE
+ ADD ENTRY
)}
{onAddMilestone && (
@@ -157,7 +159,7 @@ export default function StatsBox({
| DESCRIPTION |
- SHARES |
+ {unit.toUpperCase()} |
{priceTrackingEnabled && SWR 3% | }
{priceTrackingEnabled && SWR 4% | }
diff --git a/resources/js/components/Onboarding/OnboardingFlow.tsx b/resources/js/components/Onboarding/OnboardingFlow.tsx
index 04f6615..78aa7a0 100644
--- a/resources/js/components/Onboarding/OnboardingFlow.tsx
+++ b/resources/js/components/Onboarding/OnboardingFlow.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import AssetSetupForm from '@/components/Assets/AssetSetupForm';
-import AddPurchaseForm from '@/components/Transactions/AddPurchaseForm';
+import AddEntryForm from '@/components/Transactions/AddEntryForm';
import AddMilestoneForm from '@/components/Milestones/AddMilestoneForm';
import UpdatePriceForm from '@/components/Pricing/UpdatePriceForm';
import CreateTrackerStep from '@/components/Onboarding/CreateTrackerStep';
@@ -138,7 +138,7 @@ export default function OnboardingFlow({ onComplete }: OnboardingFlowProps) {
switch (step.id) {
case 'entries':
- return ;
+ return ;
case 'milestones':
return ;
case 'price':
diff --git a/resources/js/components/Transactions/AddEntryForm.tsx b/resources/js/components/Transactions/AddEntryForm.tsx
new file mode 100644
index 0000000..fee5885
--- /dev/null
+++ b/resources/js/components/Transactions/AddEntryForm.tsx
@@ -0,0 +1,181 @@
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import InputError from '@/components/InputError';
+import { useForm } from '@inertiajs/react';
+import { LoaderCircle } from 'lucide-react';
+import { FormEventHandler, useEffect, useState } from 'react';
+import ComponentTitle from '@/components/ui/ComponentTitle';
+
+interface EntryFormData {
+ date: string;
+ quantity: string;
+ unit_price: string;
+ total_cost: string;
+ [key: string]: string;
+}
+
+interface AddEntryFormProps {
+ unit?: string;
+ priceTrackingEnabled?: boolean;
+ onSuccess?: () => void;
+ onCancel?: () => void;
+}
+
+interface EntrySummary {
+ total_quantity: number;
+ total_cost: number;
+ average_cost_per_unit: number;
+}
+
+export default function AddEntryForm({ unit = 'units', priceTrackingEnabled = false, onSuccess, onCancel }: AddEntryFormProps) {
+ const { data, setData, post, processing, errors, reset } = useForm({
+ date: new Date().toISOString().split('T')[0],
+ quantity: '',
+ unit_price: '',
+ total_cost: '',
+ });
+
+ const [currentHoldings, setCurrentHoldings] = useState(null);
+
+ useEffect(() => {
+ const fetchSummary = async () => {
+ try {
+ const response = await fetch('/entries/summary');
+ if (response.ok) {
+ const summary = await response.json();
+ setCurrentHoldings(summary);
+ }
+ } catch (error) {
+ console.error('Failed to fetch entry summary:', error);
+ }
+ };
+
+ fetchSummary();
+ }, []);
+
+ // Auto-calculate total cost when quantity or unit_price changes
+ useEffect(() => {
+ if (data.quantity && data.unit_price) {
+ const quantity = parseFloat(data.quantity);
+ const unitPrice = parseFloat(data.unit_price);
+
+ if (!isNaN(quantity) && !isNaN(unitPrice)) {
+ setData('total_cost', (quantity * unitPrice).toFixed(2));
+ }
+ }
+ }, [data.quantity, data.unit_price, setData]);
+
+ const submit: FormEventHandler = (e) => {
+ e.preventDefault();
+
+ post(route('entries.store'), {
+ onSuccess: () => {
+ reset();
+ setData('date', new Date().toISOString().split('T')[0]);
+ if (onSuccess) onSuccess();
+ },
+ });
+ };
+
+ return (
+
+
+
ADD ENTRY
+ {currentHoldings && currentHoldings.total_quantity > 0 && (
+
+ [CURRENT] {currentHoldings.total_quantity.toFixed(6)} {unit}
+ {priceTrackingEnabled && ` • €${currentHoldings.total_cost.toFixed(2)} spent`}
+
+ )}
+
+
+
+ );
+}
diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx
index de0b334..d11d379 100644
--- a/resources/js/pages/dashboard.tsx
+++ b/resources/js/pages/dashboard.tsx
@@ -23,12 +23,20 @@ interface Milestone {
created_at: string;
}
-interface CurrentAsset {
+interface TrackerAsset {
id: number;
symbol: string;
full_name: string | null;
}
+interface Tracker {
+ id: number;
+ label: string;
+ unit: string;
+ price_tracking_enabled: boolean;
+ asset: TrackerAsset | null;
+}
+
export default function Dashboard() {
const [purchaseData, setPurchaseData] = useState({
total_shares: 0,
@@ -47,7 +55,8 @@ export default function Dashboard() {
const [activeForm, setActiveForm] = useState<'purchase' | 'milestone' | 'price' | null>(null);
const [loading, setLoading] = useState(true);
const [needsOnboarding, setNeedsOnboarding] = useState(false);
- const [currentAsset, setCurrentAsset] = useState(null);
+ const [tracker, setTracker] = useState(null);
+ const [currentAsset, setCurrentAsset] = useState(null);
const [priceTrackingEnabled, setPriceTrackingEnabled] = useState(false);
// Fetch entry summary, current price, milestones, and check onboarding
@@ -86,9 +95,10 @@ export default function Dashboard() {
}
if (trackerResponse.ok) {
- const tracker = await trackerResponse.json();
- setCurrentAsset(tracker?.asset ?? null);
- setPriceTrackingEnabled(tracker?.price_tracking_enabled ?? false);
+ const trackerData = await trackerResponse.json();
+ setTracker(trackerData);
+ setCurrentAsset(trackerData?.asset ?? null);
+ setPriceTrackingEnabled(trackerData?.price_tracking_enabled ?? false);
}
setNeedsOnboarding(totalQuantity === 0 || milestonesCount === 0);
@@ -263,6 +273,7 @@ export default function Dashboard() {
@@ -270,7 +281,7 @@ export default function Dashboard() {
{/* Box 2: Progress Bar (toggleable) */}
setActiveForm(null)}
onPurchaseSuccess={handlePurchaseSuccess}
onMilestoneSuccess={handleMilestoneSuccess}