refactor(component): atomize QuantDataGrid with GridHeaderToolbar, ReturnChip, and TickerBadge atomic components
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 9s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 11s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 20s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 11s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 8s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 9s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 11s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 20s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 11s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 8s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { AgGridVue } from 'ag-grid-vue3';
|
import { AgGridVue } from 'ag-grid-vue3';
|
||||||
|
import GridHeaderToolbar from './grid/GridHeaderToolbar.vue';
|
||||||
import type { ColDef, GridApi, GridReadyEvent, CellValueChangedEvent, RowSelectionOptions } from 'ag-grid-community';
|
import type { ColDef, GridApi, GridReadyEvent, CellValueChangedEvent, RowSelectionOptions } from 'ag-grid-community';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -12,13 +13,8 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits(['row-selected', 'cell-value-changed', 'cell-double-clicked']);
|
const emit = defineEmits(['row-selected', 'cell-value-changed', 'cell-double-clicked']);
|
||||||
const gridApi = ref<GridApi | null>(null);
|
const gridApi = ref<GridApi | null>(null);
|
||||||
|
|
||||||
const onCellDoubleClicked = (event: any) => {
|
|
||||||
emit('cell-double-clicked', event);
|
|
||||||
};
|
|
||||||
const selectedCount = ref(0);
|
const selectedCount = ref(0);
|
||||||
const quickFilterText = ref('');
|
const quickFilterText = ref('');
|
||||||
const isExportMenuOpen = ref(false);
|
|
||||||
|
|
||||||
// AG Grid v36 rowSelection deprecation 대응
|
// AG Grid v36 rowSelection deprecation 대응
|
||||||
const normalizedRowSelection = computed<RowSelectionOptions>(() => {
|
const normalizedRowSelection = computed<RowSelectionOptions>(() => {
|
||||||
@@ -43,6 +39,10 @@ const onSelectionChanged = () => {
|
|||||||
emit('row-selected', selectedRowPayload(selectedData));
|
emit('row-selected', selectedRowPayload(selectedData));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onCellDoubleClicked = (event: any) => {
|
||||||
|
emit('cell-double-clicked', event);
|
||||||
|
};
|
||||||
|
|
||||||
const selectedRowPayload = (selectedData: any[]) => {
|
const selectedRowPayload = (selectedData: any[]) => {
|
||||||
if (props.rowSelection === 'multiple' || (typeof props.rowSelection === 'object' && props.rowSelection?.mode === 'multiRow')) {
|
if (props.rowSelection === 'multiple' || (typeof props.rowSelection === 'object' && props.rowSelection?.mode === 'multiRow')) {
|
||||||
return selectedData;
|
return selectedData;
|
||||||
@@ -68,7 +68,6 @@ const autoSizeColumns = () => {
|
|||||||
|
|
||||||
const exportToCsv = () => {
|
const exportToCsv = () => {
|
||||||
if (!gridApi.value) return;
|
if (!gridApi.value) return;
|
||||||
isExportMenuOpen.value = false;
|
|
||||||
gridApi.value.exportDataAsCsv({
|
gridApi.value.exportDataAsCsv({
|
||||||
fileName: `${props.filename || 'quant_grid_export'}_${new Date().toISOString().substring(0, 10)}.csv`
|
fileName: `${props.filename || 'quant_grid_export'}_${new Date().toISOString().substring(0, 10)}.csv`
|
||||||
});
|
});
|
||||||
@@ -79,52 +78,16 @@ defineExpose({ exportToExcel: exportToCsv, resetGridState, autoSizeColumns, grid
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="quant-grid-masterpiece-container flex flex-col h-full w-full border border-slate-300 bg-white rounded-md overflow-hidden shadow-sm">
|
<div class="quant-grid-masterpiece-container flex flex-col h-full w-full border border-slate-300 bg-white rounded-md overflow-hidden shadow-sm">
|
||||||
<!-- Premium Trading-Room Data Grid Header Toolbar -->
|
<!-- Componentized Grid Header Toolbar (GridHeaderToolbar) -->
|
||||||
<div class="grid-header-toolbar">
|
<GridHeaderToolbar
|
||||||
<div class="header-left-info">
|
v-model:quickFilterText="quickFilterText"
|
||||||
<span class="grid-badge total-badge">
|
:totalCount="rowData.length"
|
||||||
📊 총 <strong>{{ rowData.length }}</strong> 건
|
:selectedCount="selectedCount"
|
||||||
</span>
|
:columnCount="columnDefs.length"
|
||||||
<span class="grid-badge selected-badge" :class="{ active: selectedCount > 0 }">
|
@reset="resetGridState"
|
||||||
✅ 선택 <strong>{{ selectedCount }}</strong> 건
|
@autoSize="autoSizeColumns"
|
||||||
</span>
|
@exportCsv="exportToCsv"
|
||||||
<span class="grid-badge col-count-badge">
|
/>
|
||||||
📐 컬럼 <strong>{{ columnDefs.length }}</strong> 개
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="header-right-actions">
|
|
||||||
<!-- 렌즈 퀵 필터 검색창 -->
|
|
||||||
<div class="quick-search-box">
|
|
||||||
<span class="search-icon">🔍</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
v-model="quickFilterText"
|
|
||||||
placeholder="그리드 즉시 필터..."
|
|
||||||
class="quick-search-input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="grid-btn btn-reset" @click="resetGridState" title="필터/정렬 초기화">
|
|
||||||
🔄 초기화
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="grid-btn btn-autosize" @click="autoSizeColumns" title="컬럼 너비 자동 맞춤">
|
|
||||||
📐 자동 맞춤
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- 엑셀 내보내기 팝오버 -->
|
|
||||||
<div class="export-popover-wrapper">
|
|
||||||
<button class="grid-btn btn-export" @click="isExportMenuOpen = !isExportMenuOpen">
|
|
||||||
📥 엑셀 내보내기 ▼
|
|
||||||
</button>
|
|
||||||
<div v-if="isExportMenuOpen" class="export-dropdown-menu">
|
|
||||||
<button @click="exportToCsv">📄 CSV 파일 (.csv) 내보내기</button>
|
|
||||||
<button @click="exportToCsv">📊 Excel 호환 (.xlsx) 내보내기</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- AG Grid Container (v36 legacy theme & object rowSelection) -->
|
<!-- AG Grid Container (v36 legacy theme & object rowSelection) -->
|
||||||
<div class="flex-1 ag-theme-alpine w-full">
|
<div class="flex-1 ag-theme-alpine w-full">
|
||||||
@@ -152,125 +115,6 @@ defineExpose({ exportToExcel: exportToCsv, resetGridState, autoSizeColumns, grid
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.grid-header-toolbar {
|
|
||||||
background: linear-gradient(135deg, #1E293B, #0F172A);
|
|
||||||
color: white;
|
|
||||||
padding: 6px 12px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid #334155;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-left-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-badge {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-weight: 700;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-badge { background: #334155; color: #E2E8F0; }
|
|
||||||
.selected-badge { background: #475569; color: #94A3B8; }
|
|
||||||
.selected-badge.active { background: #166534; color: #DCFCE7; }
|
|
||||||
.col-count-badge { background: #1E3A8A; color: #BFDBFE; }
|
|
||||||
|
|
||||||
.header-right-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-search-box {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-icon {
|
|
||||||
position: absolute;
|
|
||||||
left: 6px;
|
|
||||||
font-size: 0.65rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-search-input {
|
|
||||||
background: #334155;
|
|
||||||
color: white;
|
|
||||||
border: 1px solid #475569;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 3px 6px 3px 22px;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
width: 140px;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-search-input::placeholder { color: #94A3B8; }
|
|
||||||
.quick-search-input:focus { border-color: #3B82F6; background: #1E293B; }
|
|
||||||
|
|
||||||
.grid-btn {
|
|
||||||
padding: 3px 8px;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
font-weight: 700;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: white;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-reset { background: #475569; }
|
|
||||||
.btn-reset:hover { background: #64748B; }
|
|
||||||
|
|
||||||
.btn-autosize { background: #2563EB; }
|
|
||||||
.btn-autosize:hover { background: #1D4ED8; }
|
|
||||||
|
|
||||||
.btn-export { background: #166534; }
|
|
||||||
.btn-export:hover { background: #15803D; }
|
|
||||||
|
|
||||||
.export-popover-wrapper { position: relative; }
|
|
||||||
|
|
||||||
.export-dropdown-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
right: 0;
|
|
||||||
margin-top: 4px;
|
|
||||||
background: white;
|
|
||||||
border: 1px solid #CBD5E1;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
z-index: 100;
|
|
||||||
width: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.export-dropdown-menu button {
|
|
||||||
padding: 8px 12px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
text-align: left;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #334155;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.export-dropdown-menu button:hover {
|
|
||||||
background: #F1F5F9;
|
|
||||||
color: #2563EB;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ag-theme-alpine {
|
.ag-theme-alpine {
|
||||||
--ag-header-background-color: #f8f9fa;
|
--ag-header-background-color: #f8f9fa;
|
||||||
--ag-selected-row-background-color: rgba(41, 128, 185, 0.1);
|
--ag-selected-row-background-color: rgba(41, 128, 185, 0.1);
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<!-- GridHeaderToolbar.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
totalCount: number;
|
||||||
|
selectedCount: number;
|
||||||
|
columnCount: number;
|
||||||
|
quickFilterText: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:quickFilterText', val: string): void;
|
||||||
|
(e: 'reset'): void;
|
||||||
|
(e: 'autoSize'): void;
|
||||||
|
(e: 'exportCsv'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const isExportMenuOpen = ref(false);
|
||||||
|
|
||||||
|
const handleExport = () => {
|
||||||
|
isExportMenuOpen.value = false;
|
||||||
|
emit('exportCsv');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="grid-header-toolbar">
|
||||||
|
<div class="header-left-info">
|
||||||
|
<span class="grid-badge total-badge">
|
||||||
|
📊 총 <strong>{{ totalCount }}</strong> 건
|
||||||
|
</span>
|
||||||
|
<span class="grid-badge selected-badge" :class="{ active: selectedCount > 0 }">
|
||||||
|
✅ 선택 <strong>{{ selectedCount }}</strong> 건
|
||||||
|
</span>
|
||||||
|
<span class="grid-badge col-count-badge">
|
||||||
|
📐 컬럼 <strong>{{ columnCount }}</strong> 개
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header-right-actions">
|
||||||
|
<!-- 렌즈 퀵 필터 검색창 -->
|
||||||
|
<div class="quick-search-box">
|
||||||
|
<span class="search-icon">🔍</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
:value="quickFilterText"
|
||||||
|
@input="emit('update:quickFilterText', ($event.target as HTMLInputElement).value)"
|
||||||
|
placeholder="그리드 즉시 필터..."
|
||||||
|
class="quick-search-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="grid-btn btn-reset" @click="emit('reset')" title="필터/정렬 초기화">
|
||||||
|
🔄 초기화
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="grid-btn btn-autosize" @click="emit('autoSize')" title="컬럼 너비 자동 맞춤">
|
||||||
|
📐 자동 맞춤
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 엑셀 내보내기 팝오버 -->
|
||||||
|
<div class="export-popover-wrapper">
|
||||||
|
<button class="grid-btn btn-export" @click="isExportMenuOpen = !isExportMenuOpen">
|
||||||
|
📥 엑셀 내보내기 ▼
|
||||||
|
</button>
|
||||||
|
<div v-if="isExportMenuOpen" class="export-dropdown-menu">
|
||||||
|
<button @click="handleExport">📄 CSV 파일 (.csv) 내보내기</button>
|
||||||
|
<button @click="handleExport">📊 Excel 호환 (.xlsx) 내보내기</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.grid-header-toolbar {
|
||||||
|
background: linear-gradient(135deg, #1E293B, #0F172A);
|
||||||
|
color: white;
|
||||||
|
padding: 6px 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #334155;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left-info { display: flex; align-items: center; gap: 6px; }
|
||||||
|
|
||||||
|
.grid-badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-badge { background: #334155; color: #E2E8F0; }
|
||||||
|
.selected-badge { background: #475569; color: #94A3B8; }
|
||||||
|
.selected-badge.active { background: #166534; color: #DCFCE7; }
|
||||||
|
.col-count-badge { background: #1E3A8A; color: #BFDBFE; }
|
||||||
|
|
||||||
|
.header-right-actions { display: flex; align-items: center; gap: 6px; }
|
||||||
|
|
||||||
|
.quick-search-box { position: relative; display: flex; align-items: center; }
|
||||||
|
.search-icon { position: absolute; left: 6px; font-size: 0.65rem; }
|
||||||
|
|
||||||
|
.quick-search-input {
|
||||||
|
background: #334155;
|
||||||
|
color: white;
|
||||||
|
border: 1px solid #475569;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px 6px 3px 22px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
width: 140px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-search-input::placeholder { color: #94A3B8; }
|
||||||
|
.quick-search-input:focus { border-color: #3B82F6; background: #1E293B; }
|
||||||
|
|
||||||
|
.grid-btn {
|
||||||
|
padding: 3px 8px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-reset { background: #475569; }
|
||||||
|
.btn-reset:hover { background: #64748B; }
|
||||||
|
.btn-autosize { background: #2563EB; }
|
||||||
|
.btn-autosize:hover { background: #1D4ED8; }
|
||||||
|
.btn-export { background: #166534; }
|
||||||
|
.btn-export:hover { background: #15803D; }
|
||||||
|
|
||||||
|
.export-popover-wrapper { position: relative; }
|
||||||
|
|
||||||
|
.export-dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 4px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #CBD5E1;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 100;
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dropdown-menu button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #334155;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-dropdown-menu button:hover { background: #F1F5F9; color: #2563EB; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<!-- ReturnChip.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
value?: number | null;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const chipClass = computed(() => {
|
||||||
|
if (props.value === undefined || props.value === null || props.value === 0) return 'zero';
|
||||||
|
return props.value > 0 ? 'pos' : 'neg';
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedText = computed(() => {
|
||||||
|
if (props.value === undefined || props.value === null || props.value === 0) return '0.00%';
|
||||||
|
const prefix = props.value > 0 ? '+' : '';
|
||||||
|
return `${prefix}${props.value}%`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span class="return-chip" :class="chipClass">
|
||||||
|
{{ formattedText }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.return-chip {
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.return-chip.pos { background: #DCFCE7; color: #15803D; }
|
||||||
|
.return-chip.neg { background: #FEE2E2; color: #B91C1C; }
|
||||||
|
.return-chip.zero { background: #F1F5F9; color: #64748B; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<!-- TickerBadge.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
ticker?: string;
|
||||||
|
symbolName?: string;
|
||||||
|
fallbackKey?: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="ticker-badge-wrapper">
|
||||||
|
<span v-if="ticker" class="ticker-badge">{{ ticker }}</span>
|
||||||
|
<strong class="symbol-name">{{ symbolName || fallbackKey }}</strong>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ticker-badge-wrapper {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ticker-badge {
|
||||||
|
background: #1E293B;
|
||||||
|
color: #F1F5F9;
|
||||||
|
padding: 1px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbol-name {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1E293B;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user