refactor(frontend): extract OverviewTab safely
- New component: frontend/src/lib/components/OverviewTab.svelte
- Props: selectedNode, notes, worklog, formatDate, nodeKindLabel
- Events: createNote, addFile, createAction, switchTab, openNote
- Pure display component, no internal state
- App.svelte changes:
- Replaced inline overview markup with <OverviewTab>
- Removed overview-related CSS (overview, meta-grid, qa-btn, etc.)
- openCreateNote() call replaced with setActiveTab('notes') only
(create note is now handled by NotesTab component)
- Build: npm run build ✅, go test ./... ✅
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -19,8 +19,8 @@
|
||||
background: #13131f;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/main-nSPvOkeD.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-CUROQg6p.css">
|
||||
<script type="module" crossorigin src="/assets/main-C0_3Dz8C.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-t8j1WzN6.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
+12
-61
@@ -19,6 +19,7 @@
|
||||
import NoteEditorPanel from './lib/components/notes/NoteEditorPanel.svelte'
|
||||
import InternalLinkPicker from './lib/components/notes/InternalLinkPicker.svelte'
|
||||
import NotesTab from './lib/components/notes/NotesTab.svelte'
|
||||
import OverviewTab from './lib/components/OverviewTab.svelte'
|
||||
import FilesTab from './lib/components/files/FilesTab.svelte'
|
||||
|
||||
// ===== Wails v2 API call helper =====
|
||||
@@ -2560,50 +2561,17 @@
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
{#if activeTab === 'overview'}
|
||||
<div class="overview">
|
||||
<h2>{selectedNode.title}</h2>
|
||||
<div class="meta-grid">
|
||||
<div class="meta-item"><span class="meta-label">{t('overview.type')}</span><span>{nodeKindLabel(selectedNode.type)}</span></div>
|
||||
<div class="meta-item"><span class="meta-label">{t('overview.section')}</span><span>{selectedNode.section || '—'}</span></div>
|
||||
<div class="meta-item"><span class="meta-label">{t('overview.created')}</span><span>{formatDate(selectedNode.createdAt)}</span></div>
|
||||
</div>
|
||||
<div class="quick-actions">
|
||||
<button class="qa-btn" on:click={() => { setActiveTab('notes'); openCreateNote() }}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
||||
{t('overview.newNote')}
|
||||
</button>
|
||||
<button class="qa-btn" on:click={() => { setActiveTab('files'); if (filesTabRef) filesTabRef.addFile(); }}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
||||
{t('overview.addFile')}
|
||||
</button>
|
||||
<button class="qa-btn" on:click={openCreateAction}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
{t('overview.addAction')}
|
||||
</button>
|
||||
<button class="qa-btn" on:click={() => setActiveTab('worklog')}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||
{t('overview.logTime')}
|
||||
</button>
|
||||
</div>
|
||||
{#if notes.length > 0}
|
||||
<div class="recent-section">
|
||||
<h3>{t('overview.recentNotes')}</h3>
|
||||
{#each notes.slice(0, 5) as note}
|
||||
<div class="recent-note" role="button" tabindex="0" on:click={() => openNote(note)} on:keydown={onKeyActivate(() => openNote(note))}>
|
||||
<span>{note.title}</span><span class="recent-date">{formatDate(note.createdAt)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if worklog.length > 0}
|
||||
<div class="recent-section">
|
||||
<h3>{t('overview.recentEntries')}</h3>
|
||||
{#each worklog.slice(0, 3) as e}
|
||||
<div class="recent-entry">{e.summary} ({e.minutes} {t('worklog.min')})</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<OverviewTab
|
||||
{selectedNode}
|
||||
{notes}
|
||||
{worklog}
|
||||
{formatDate}
|
||||
{nodeKindLabel}
|
||||
on:createNote={() => { setActiveTab('notes'); }}
|
||||
on:addFile={() => { setActiveTab('files'); if (filesTabRef) filesTabRef.addFile(); }}
|
||||
on:createAction={openCreateAction}
|
||||
on:openNote={(e) => openNote(e.detail.note)}
|
||||
/>
|
||||
|
||||
{:else if activeTab === 'notes'}
|
||||
<NotesTab
|
||||
@@ -3685,23 +3653,6 @@
|
||||
.form-group input:focus { border-color: #818cf8; }
|
||||
.note-textarea { flex: 1; width: 100%; border: none; outline: none; background: #13131f; color: #e4e4ef; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 14px; line-height: 1.6; padding: 24px; resize: none; }
|
||||
|
||||
/* Overview */
|
||||
.overview { padding: 24px; }
|
||||
.overview h2 { font-size: 24px; margin-bottom: 16px; }
|
||||
.meta-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }
|
||||
.meta-item { background: #1a1a28; padding: 12px 16px; border-radius: 8px; }
|
||||
.meta-label { display: block; font-size: 11px; color: #666; margin-bottom: 4px; text-transform: uppercase; }
|
||||
.quick-actions { display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap; }
|
||||
.qa-btn { padding: 10px 16px; border: 1px solid #2a2a3c; background: #1a1a28; color: #ccc; border-radius: 8px; cursor: pointer; font-size: 13px; font-family: inherit; display: inline-flex; align-items: center; gap: 6px; }
|
||||
.qa-btn:hover { background: #222233; }
|
||||
.qa-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
.recent-section { margin-bottom: 24px; }
|
||||
.recent-section h3 { font-size: 13px; color: #666; text-transform: uppercase; margin-bottom: 8px; }
|
||||
.recent-note { padding: 8px 12px; border-radius: 6px; cursor: pointer; display: flex; justify-content: space-between; }
|
||||
.recent-note:hover { background: #1a1a28; }
|
||||
.recent-date { font-size: 11px; color: #555; }
|
||||
.recent-entry { padding: 6px 0; font-size: 13px; color: #888; border-bottom: 1px solid #1a1a28; }
|
||||
|
||||
/* Worklog tab */
|
||||
.worklog-tab { padding: 24px; }
|
||||
.worklog-toolbar { margin-bottom: 16px; }
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { t } from '../i18n'
|
||||
|
||||
// ===== Props =====
|
||||
export let selectedNode = null
|
||||
export let notes = []
|
||||
export let worklog = []
|
||||
export let formatDate = (str) => ''
|
||||
export let nodeKindLabel = (kind) => kind || ''
|
||||
|
||||
// ===== Events =====
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function createNote() {
|
||||
dispatch('createNote')
|
||||
}
|
||||
|
||||
function addFile() {
|
||||
dispatch('addFile')
|
||||
}
|
||||
|
||||
function createAction() {
|
||||
dispatch('createAction')
|
||||
}
|
||||
|
||||
function logTime() {
|
||||
dispatch('switchTab', { tab: 'worklog' })
|
||||
}
|
||||
|
||||
function openNoteHandler(note) {
|
||||
dispatch('openNote', { note })
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="overview">
|
||||
<h2>{selectedNode.title}</h2>
|
||||
<div class="meta-grid">
|
||||
<div class="meta-item"><span class="meta-label">{t('overview.type')}</span><span>{nodeKindLabel(selectedNode.type)}</span></div>
|
||||
<div class="meta-item"><span class="meta-label">{t('overview.section')}</span><span>{selectedNode.section || '—'}</span></div>
|
||||
<div class="meta-item"><span class="meta-label">{t('overview.created')}</span><span>{formatDate(selectedNode.createdAt)}</span></div>
|
||||
</div>
|
||||
<div class="quick-actions">
|
||||
<button class="qa-btn" on:click={createNote}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
||||
{t('overview.newNote')}
|
||||
</button>
|
||||
<button class="qa-btn" on:click={addFile}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
||||
{t('overview.addFile')}
|
||||
</button>
|
||||
<button class="qa-btn" on:click={createAction}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
{t('overview.addAction')}
|
||||
</button>
|
||||
<button class="qa-btn" on:click={logTime}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||
{t('overview.logTime')}
|
||||
</button>
|
||||
</div>
|
||||
{#if notes.length > 0}
|
||||
<div class="recent-section">
|
||||
<h3>{t('overview.recentNotes')}</h3>
|
||||
{#each notes.slice(0, 5) as note}
|
||||
<div class="recent-note" role="button" tabindex="0" on:click={() => openNoteHandler(note)} on:keydown={(e) => e.key === 'Enter' && openNoteHandler(note)}>
|
||||
<span>{note.title}</span><span class="recent-date">{formatDate(note.createdAt)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if worklog.length > 0}
|
||||
<div class="recent-section">
|
||||
<h3>{t('overview.recentEntries')}</h3>
|
||||
{#each worklog.slice(0, 3) as e}
|
||||
<div class="recent-entry">{e.summary} ({e.minutes} {t('worklog.min')})</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.overview { padding: 24px; }
|
||||
.overview h2 { font-size: 24px; margin-bottom: 16px; }
|
||||
.meta-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }
|
||||
.meta-item { background: #1a1a28; padding: 12px 16px; border-radius: 8px; }
|
||||
.meta-label { display: block; font-size: 11px; color: #666; margin-bottom: 4px; text-transform: uppercase; }
|
||||
.quick-actions { display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap; }
|
||||
.qa-btn { padding: 10px 16px; border: 1px solid #2a2a3c; background: #1a1a28; color: #ccc; border-radius: 8px; cursor: pointer; font-size: 13px; font-family: inherit; display: inline-flex; align-items: center; gap: 6px; }
|
||||
.qa-btn:hover { background: #222233; }
|
||||
.qa-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
.recent-section { margin-bottom: 24px; }
|
||||
.recent-section h3 { font-size: 13px; color: #666; text-transform: uppercase; margin-bottom: 8px; }
|
||||
.recent-note { padding: 8px 12px; border-radius: 6px; cursor: pointer; display: flex; justify-content: space-between; }
|
||||
.recent-note:hover { background: #1a1a28; }
|
||||
.recent-date { font-size: 11px; color: #555; }
|
||||
.recent-entry { padding: 6px 0; font-size: 13px; color: #888; border-bottom: 1px solid #1a1a28; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user