improving the table layout
This commit is contained in:
@@ -20,7 +20,8 @@
|
||||
<FilterBar :filters="filters" :result-count="profitResults.length" @set-name-filter="setNameFilter"
|
||||
@toggle-tier="toggleTier" @set-selected-item-types="setSelectedItemTypes"
|
||||
@toggle-enchantment="toggleEnchantment" @reset-enchantments="resetEnchantments"
|
||||
@toggle-variant="toggleVariant" @reset-variants="resetVariants" />
|
||||
@toggle-variant="toggleVariant" @reset-variants="resetVariants"
|
||||
@toggle-station="toggleStation" @reset-stations="resetStations" />
|
||||
|
||||
<ProfitTable :results="profitResults" :sort-state="sortState" @sort="handleSort" />
|
||||
</div>
|
||||
@@ -73,6 +74,8 @@ const {
|
||||
resetEnchantments,
|
||||
toggleVariant,
|
||||
resetVariants,
|
||||
toggleStation,
|
||||
resetStations,
|
||||
} = useFilters()
|
||||
|
||||
// Profit calculation
|
||||
|
||||
@@ -131,6 +131,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Station toggles -->
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-xs text-gray-400">Station</span>
|
||||
<div class="flex gap-0.5">
|
||||
<button class="px-2 py-1 rounded text-xs font-semibold transition-colors" :class="filters.stations === null
|
||||
? 'bg-amber-600 text-amber-100'
|
||||
: 'bg-gray-800 text-gray-500 hover:bg-gray-700'" @click="$emit('reset-stations')">All</button>
|
||||
<button v-for="s in ALL_STATIONS" :key="s"
|
||||
class="px-2 py-1 rounded text-xs font-semibold transition-colors"
|
||||
:class="isStationActive(s) ? STATION_ACTIVE_CLASS[s] : 'bg-gray-800 text-gray-600 hover:bg-gray-700'"
|
||||
@click="$emit('toggle-station', s)">{{ STATION_LABEL[s] }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Result count -->
|
||||
<span class="ml-auto text-xs text-gray-500 whitespace-nowrap">{{ resultCount }} results</span>
|
||||
|
||||
@@ -142,8 +156,8 @@
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { tierStyle, enchantStyle } from '../../utils/formatting'
|
||||
import type { FilterState, VariantType } from '../../types/filters'
|
||||
import { ALL_VARIANTS } from '../../types/filters'
|
||||
import type { Tier, Enchantment } from '../../types/crafting'
|
||||
import { ALL_VARIANTS, ALL_STATIONS } from '../../types/filters'
|
||||
import type { Tier, Enchantment, JournalType } from '../../types/crafting'
|
||||
import { TIERS, ENCHANTMENTS } from '../../data/constants'
|
||||
import { ITEM_TREE, ALL_ITEM_NAMES, getLeaves } from '../../data/itemTree'
|
||||
import type { TreeNode } from '../../data/itemTree'
|
||||
@@ -161,6 +175,8 @@ const emit = defineEmits<{
|
||||
'reset-enchantments': []
|
||||
'toggle-variant': [v: VariantType]
|
||||
'reset-variants': []
|
||||
'toggle-station': [s: JournalType]
|
||||
'reset-stations': []
|
||||
}>()
|
||||
|
||||
// ─── Category tree ────────────────────────────────────────────────────────────
|
||||
@@ -287,6 +303,23 @@ function isVariantActive(v: VariantType): boolean {
|
||||
return s === null || s.has(v)
|
||||
}
|
||||
|
||||
// ─── Station helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
const STATION_LABEL: Record<JournalType, string> = {
|
||||
warrior: 'Warrior', hunter: 'Hunter', mage: 'Mage', toolmaker: 'Toolmaker',
|
||||
}
|
||||
|
||||
const STATION_ACTIVE_CLASS: Record<JournalType, string> = {
|
||||
warrior: 'bg-red-900/60 text-red-300',
|
||||
hunter: 'bg-green-900/60 text-green-300',
|
||||
mage: 'bg-blue-900/60 text-blue-300',
|
||||
toolmaker: 'bg-orange-900/60 text-orange-300',
|
||||
}
|
||||
|
||||
function isStationActive(s: JournalType): boolean {
|
||||
return props.filters.stations === null || props.filters.stations.has(s)
|
||||
}
|
||||
|
||||
|
||||
// ─── Dropdown open states ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -26,10 +26,17 @@
|
||||
class="w-12 h-12 -my-2 rounded flex-shrink-0"
|
||||
/>
|
||||
<span class="text-sm font-medium text-gray-200">{{ result.recipe.displayName.replace(/^T\d+\.\d+\s/, '') }}</span>
|
||||
<span class="text-xs text-gray-500">{{ result.recipe.category }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Station badge -->
|
||||
<td class="px-4 py-3">
|
||||
<span class="inline-flex items-center justify-center px-2 h-6 rounded text-xs font-semibold whitespace-nowrap"
|
||||
:class="STATION[result.recipe.journalType].cls">
|
||||
{{ STATION[result.recipe.journalType].label }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- Tier badge -->
|
||||
<td class="px-4 py-3">
|
||||
<span
|
||||
@@ -156,7 +163,7 @@
|
||||
|
||||
<!-- Expanded detail row -->
|
||||
<tr v-if="expanded" class="border-b border-gray-700/50 bg-gray-900/60">
|
||||
<td colspan="13" class="px-6 py-4">
|
||||
<td colspan="14" class="px-6 py-4">
|
||||
<div class="flex gap-8">
|
||||
|
||||
<!-- Ingredients breakdown -->
|
||||
@@ -251,7 +258,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import type { ProfitResult, Tier } from '../../types/crafting'
|
||||
import type { ProfitResult, Tier, JournalType } from '../../types/crafting'
|
||||
import { formatSilver, formatLastUpdated, tierEnchantStyle, itemImageUrl } from '../../utils/formatting'
|
||||
import { useAlbionPrices } from '../../composables/useAlbionPrices'
|
||||
import { useFilters } from '../../composables/useFilters'
|
||||
@@ -267,6 +274,13 @@ const { getManualEntry, setManualPrice, clearManualPrice } = useAlbionPrices()
|
||||
const { filters } = useFilters()
|
||||
const { upsert, remove, getQty, inOrder } = useProductionOrder()
|
||||
|
||||
const STATION: Record<JournalType, { label: string; cls: string }> = {
|
||||
warrior: { label: 'Warrior', cls: 'bg-red-900/30 border border-red-700/40 text-red-400' },
|
||||
hunter: { label: 'Hunter', cls: 'bg-green-900/30 border border-green-700/40 text-green-400' },
|
||||
mage: { label: 'Mage', cls: 'bg-blue-900/30 border border-blue-700/40 text-blue-400' },
|
||||
toolmaker: { label: 'Toolmaker', cls: 'bg-orange-900/30 border border-orange-700/40 text-orange-400' },
|
||||
}
|
||||
|
||||
const VARIANT_INFO: Record<string, { label: string; cls: string }> = {
|
||||
avalon: { label: 'Avalon', cls: 'bg-violet-600/30 text-violet-300 border border-violet-600/40' },
|
||||
crystal: { label: 'Crystal', cls: 'bg-cyan-600/30 text-cyan-300 border border-cyan-600/40' },
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<thead class="sticky top-0 z-10">
|
||||
<!-- Group row -->
|
||||
<tr class="bg-gray-800 border-b border-gray-700/50">
|
||||
<th colspan="4" class="bg-gray-800" />
|
||||
<th colspan="5" class="bg-gray-800" />
|
||||
<th colspan="4" class="px-4 py-1.5 text-center text-[10px] font-semibold text-gray-500 uppercase tracking-wider border-l border-gray-700/60">
|
||||
No Focus
|
||||
</th>
|
||||
@@ -68,6 +68,7 @@ import type { SortField, SortState } from '../../types/crafting'
|
||||
const leftCols = [
|
||||
{ field: 'variantType' as SortField, label: 'Variant', sortable: true },
|
||||
{ field: 'displayName' as SortField, label: 'Item', sortable: true },
|
||||
{ field: 'station' as SortField, label: 'Station', sortable: true },
|
||||
{ field: 'tier' as SortField, label: 'Tier', sortable: true },
|
||||
]
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ export function useCraftingProfit(
|
||||
if (!f.tiers.has(recipe.tier)) continue
|
||||
if (f.enchantments !== null && !f.enchantments.has(recipe.enchantment)) continue
|
||||
if (f.variants !== null && !f.variants.has(variantOf(recipe.outputItemId))) continue
|
||||
if (f.stations !== null && !f.stations.has(recipe.journalType)) continue
|
||||
const baseName = recipe.displayName.replace(/^T\d+\.\d /, '')
|
||||
if (f.selectedItemTypes !== null && !f.selectedItemTypes.has(baseName)) continue
|
||||
if (nameLower && !recipe.displayName.toLowerCase().includes(nameLower)) continue
|
||||
@@ -127,6 +128,10 @@ export function useCraftingProfit(
|
||||
aVal = variantRank(a.recipe.outputItemId)
|
||||
bVal = variantRank(b.recipe.outputItemId)
|
||||
break
|
||||
case 'station':
|
||||
aVal = a.recipe.journalType
|
||||
bVal = b.recipe.journalType
|
||||
break
|
||||
default:
|
||||
aVal = a.effectiveMaterialCost
|
||||
bVal = b.effectiveMaterialCost
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ref, watch } from 'vue'
|
||||
import type { FilterState, VariantType } from '../types/filters'
|
||||
import { ALL_VARIANTS } from '../types/filters'
|
||||
import type { Tier, Enchantment } from '../types/crafting'
|
||||
import { ALL_VARIANTS, ALL_STATIONS } from '../types/filters'
|
||||
import type { Tier, Enchantment, JournalType } from '../types/crafting'
|
||||
import type { AlbionCity } from '../types/api'
|
||||
import { ENCHANTMENTS, TIERS } from '../data/constants'
|
||||
|
||||
@@ -14,6 +14,7 @@ interface StoredFilters {
|
||||
tiers?: Tier[]
|
||||
enchantments?: Enchantment[] | null
|
||||
variants?: VariantType[] | null
|
||||
stations?: JournalType[] | null
|
||||
selectedItemTypes?: string[] | null
|
||||
}
|
||||
|
||||
@@ -33,6 +34,7 @@ function buildInitialState(): FilterState {
|
||||
tiers: s.tiers ? new Set(s.tiers) : new Set(TIERS),
|
||||
enchantments: s.enchantments === undefined ? null : (s.enchantments === null ? null : new Set(s.enchantments)),
|
||||
variants: s.variants === undefined ? null : (s.variants === null ? null : new Set(s.variants)),
|
||||
stations: s.stations === undefined ? null : (s.stations === null ? null : new Set(s.stations)),
|
||||
selectedItemTypes: s.selectedItemTypes === undefined ? null : (s.selectedItemTypes === null ? null : new Set(s.selectedItemTypes)),
|
||||
nameFilter: '',
|
||||
}
|
||||
@@ -49,6 +51,7 @@ watch(filters, () => {
|
||||
tiers: [...f.tiers],
|
||||
enchantments: f.enchantments === null ? null : [...f.enchantments],
|
||||
variants: f.variants === null ? null : [...f.variants],
|
||||
stations: f.stations === null ? null : [...f.stations],
|
||||
selectedItemTypes: f.selectedItemTypes === null ? null : [...f.selectedItemTypes],
|
||||
}
|
||||
try {
|
||||
@@ -110,6 +113,21 @@ function resetVariants() {
|
||||
filters.value.variants = null
|
||||
}
|
||||
|
||||
function toggleStation(station: JournalType) {
|
||||
const current = filters.value.stations
|
||||
const next = current === null ? new Set(ALL_STATIONS) : new Set(current)
|
||||
if (next.has(station)) {
|
||||
next.delete(station)
|
||||
} else {
|
||||
next.add(station)
|
||||
}
|
||||
filters.value.stations = next.size === ALL_STATIONS.length ? null : next
|
||||
}
|
||||
|
||||
function resetStations() {
|
||||
filters.value.stations = null
|
||||
}
|
||||
|
||||
export function useFilters() {
|
||||
return {
|
||||
filters,
|
||||
@@ -121,5 +139,7 @@ export function useFilters() {
|
||||
resetEnchantments,
|
||||
toggleVariant,
|
||||
resetVariants,
|
||||
toggleStation,
|
||||
resetStations,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export type JournalType = 'warrior' | 'mage' | 'hunter' | 'toolmaker'
|
||||
|
||||
export type FameType = 'twoHanded' | 'oneHanded' | 'armorChest' | 'small'
|
||||
|
||||
export type SortField = 'materialCost' | 'displayName' | 'tier' | 'variantType'
|
||||
export type SortField = 'materialCost' | 'displayName' | 'tier' | 'variantType' | 'station'
|
||||
|
||||
export type SortDirection = 'asc' | 'desc'
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { AlbionCity } from './api'
|
||||
import type { Tier, Enchantment } from './crafting'
|
||||
import type { Tier, Enchantment, JournalType } from './crafting'
|
||||
|
||||
export type VariantType = 'basic' | 'artifact' | 'avalon' | 'crystal'
|
||||
|
||||
export const ALL_VARIANTS: VariantType[] = ['basic', 'artifact', 'avalon', 'crystal']
|
||||
|
||||
export const ALL_STATIONS: JournalType[] = ['warrior', 'hunter', 'mage', 'toolmaker']
|
||||
|
||||
export interface FilterState {
|
||||
city: AlbionCity
|
||||
tiers: Set<Tier>
|
||||
@@ -12,4 +14,5 @@ export interface FilterState {
|
||||
nameFilter: string
|
||||
enchantments: Set<Enchantment> | null
|
||||
variants: Set<VariantType> | null
|
||||
stations: Set<JournalType> | null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user