diff --git a/src/components/AddItemModal.vue b/src/components/AddItemModal.vue index ba3ab07..6d3253b 100644 --- a/src/components/AddItemModal.vue +++ b/src/components/AddItemModal.vue @@ -32,6 +32,22 @@ + +
+ +
+ +
+
+
@@ -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'], @@ -259,6 +283,7 @@ const form = ref({ priceVariant: '', gradingCompany: 'PSA', grade: '10', + condition: 'NM', purchasePrice: null, currentValue: null, quantity: 1, @@ -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') { @@ -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; diff --git a/src/stores/portfolio.js b/src/stores/portfolio.js index f556724..b606853 100644 --- a/src/stores/portfolio.js +++ b/src/stores/portfolio.js @@ -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' @@ -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) @@ -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) @@ -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 } diff --git a/src/utils/conditionMultipliers.js b/src/utils/conditionMultipliers.js new file mode 100644 index 0000000..3065710 --- /dev/null +++ b/src/utils/conditionMultipliers.js @@ -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; +}