Skip to content
Open
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
50 changes: 50 additions & 0 deletions src/components/AddItemModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@
</div>
</div>

<!-- Condition selector for Raw Cards -->
<div v-if="itemType === 'card'" class="form-group mt-3">
<label class="form-label">Card Condition</label>
<div class="condition-chips">
<button
v-for="c in conditions"
:key="c.value"
class="condition-chip"
:class="{ active: form.condition === c.value }"
@click="form.condition = c.value"
>
{{ c.label }}
</button>
</div>
</div>

<!-- Graded specific -->
<div v-if="itemType === 'graded'" class="form-row mt-3">
<div class="form-group">
Expand Down Expand Up @@ -200,6 +216,14 @@ const types = [
{ value: 'sealed', label: 'Sealed Product', icon: '📦' },
]

const conditions = [
{ value: 'NM', label: 'NM' },
{ value: 'LP', label: 'LP' },
{ value: 'MP', label: 'MP' },
{ value: 'HP', label: 'HP' },
{ value: 'DMG', label: 'DMG' },
]

const gradesByCompany = {
PSA: ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1'],
BGS: ['10', '9.5', '9', '8.5', '8', '7.5', '7', '6', '5', '4', '3', '2', '1'],
Expand Down Expand Up @@ -259,6 +283,7 @@ const form = ref({
priceVariant: '',
gradingCompany: 'PSA',
grade: '10',
condition: 'NM',
purchasePrice: null,
currentValue: null,
quantity: 1,
Expand Down Expand Up @@ -314,6 +339,7 @@ function submit() {
subtypes: props.card.subtypes,
},
priceVariant: form.value.priceVariant,
condition: form.value.condition,
currentMarketPrice: currentPrice.value,
}
} else if (itemType.value === 'graded') {
Expand Down Expand Up @@ -390,6 +416,30 @@ watch(() => props.card, () => {
.type-tab:hover { background: var(--bg-hover); color: var(--text-primary); }
.type-tab.active { background: var(--accent-dim); color: var(--accent); border-color: var(--accent); }

.condition-chips {
display: flex;
gap: 8px;
margin-top: 4px;
}
.condition-chip {
flex: 1;
padding: 6px 4px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--bg-card);
color: var(--text-secondary);
font-size: 11px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s;
}
.condition-chip:hover { border-color: var(--text-muted); }
.condition-chip.active {
background: var(--accent);
color: white;
border-color: var(--accent);
}

.card-preview {
display: flex;
align-items: center;
Expand Down
28 changes: 24 additions & 4 deletions src/stores/portfolio.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { loadState, saveState, isStale as _isStale, hasNeverPriced as _hasNeverPriced } from '../db'
import { getAdjustedPrice } from '../utils/conditionMultipliers'

// Legacy localStorage keys (for migration)
const LEGACY_PORTFOLIOS_KEY = 'rarebox_portfolios'
Expand Down Expand Up @@ -152,9 +153,14 @@ export const usePortfolioStore = defineStore('portfolio', () => {
return portfolios.value.reduce((total, p) => {
return total + p.items.reduce((sum, item) => {
const qty = item.quantity || 1
const value = item.type === 'card'
const basePrice = item.type === 'card'
? (item.currentMarketPrice || item.purchasePrice || 0)
: (item.currentValue || item.purchasePrice || 0)

const value = item.type === 'card'
? getAdjustedPrice(basePrice, item.condition, !!item.grading)
: basePrice

return sum + value * qty
}, 0)
}, 0)
Expand Down Expand Up @@ -273,16 +279,25 @@ export const usePortfolioStore = defineStore('portfolio', () => {
const totalCost = items.reduce((s, i) => s + (i.purchasePrice || 0) * (i.quantity || 1), 0)
const totalValue = items.reduce((s, i) => {
const qty = i.quantity || 1
const val = i.type === 'card'
const basePrice = i.type === 'card'
? (i.currentMarketPrice || i.purchasePrice || 0)
: (i.currentValue || i.purchasePrice || 0)

const val = i.type === 'card'
? getAdjustedPrice(basePrice, i.condition, !!i.grading)
: basePrice

return s + val * qty
}, 0)
const gain = totalValue - totalCost
const gainPct = totalCost > 0 ? (gain / totalCost) * 100 : 0
const topGainer = items.reduce((best, item) => {
const cost = (item.purchasePrice || 0)
const val = item.type === 'card' ? (item.currentMarketPrice || cost) : (item.currentValue || cost)
const basePrice = item.type === 'card' ? (item.currentMarketPrice || cost) : (item.currentValue || cost)
const val = item.type === 'card'
? getAdjustedPrice(basePrice, item.condition, !!item.grading)
: basePrice

const g = cost > 0 ? (val - cost) / cost * 100 : 0
return g > (best?.gain || -Infinity) ? { item, gain: g } : best
}, null)
Expand All @@ -301,9 +316,14 @@ export const usePortfolioStore = defineStore('portfolio', () => {

const values = {}
for (const item of portfolio.items) {
const price = item.type === 'card'
const basePrice = item.type === 'card'
? (item.currentMarketPrice || item.purchasePrice || 0)
: (item.currentValue || item.purchasePrice || 0)

const price = item.type === 'card'
? getAdjustedPrice(basePrice, item.condition, !!item.grading)
: basePrice

if (price > 0) values[item.id] = price
}

Expand Down
27 changes: 27 additions & 0 deletions src/utils/conditionMultipliers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Condition multipliers for raw cards.
* Based on industry standards (e.g., Card Ladder).
* NM (Near Mint) is the baseline (1.0).
*/
export const CONDITION_MULTIPLIERS = {
'NM': 1.0,
'LP': 0.8,
'MP': 0.5,
'HP': 0.3,
'DMG': 0.15
};

/**
* Calculates the adjusted price based on condition.
* Only applies to raw cards. Graded items are untouched.
*
* @param {number} price - The raw market price (assumed NM)
* @param {string} condition - The condition code (NM, LP, etc.)
* @param {boolean} isGraded - Whether the item is a graded slab
* @returns {number} The condition-adjusted price
*/
export function getAdjustedPrice(price, condition = 'NM', isGraded = false) {
if (isGraded) return price;
const multiplier = CONDITION_MULTIPLIERS[condition] || 1.0;
return price * multiplier;
}