Add pending apps and frontend components
- apps/captain-mobile: Mobile API service - apps/flow-ui: Flow UI application - apps/mindlink: Mindlink application - apps/storage: Storage API and workers - apps/tzzr-cli: TZZR CLI tool - deck-frontend/backups: Historical TypeScript versions - hst-frontend: Standalone HST frontend Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { View } from '../View.ts';
|
||||
import { getName, getFullImg, copyMrf, delegateEvent } from '@/utils/index.ts';
|
||||
import { fetchChildren, fetchRelated } from '@/api/index.ts';
|
||||
import type { Store } from '@/state/store.ts';
|
||||
import type { AppState, Tag } from '@/types/index.ts';
|
||||
|
||||
export class DetailPanel extends View {
|
||||
private panelEl: HTMLElement;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
store: Store<AppState>
|
||||
) {
|
||||
super(container, store);
|
||||
this.panelEl = container;
|
||||
}
|
||||
|
||||
async showDetail(mrf: string): Promise<void> {
|
||||
const state = this.getState();
|
||||
const tag = state.tags.find(t => t.mrf === mrf);
|
||||
if (!tag) return;
|
||||
|
||||
this.setState({ selectedTag: tag });
|
||||
this.panelEl.classList.add('open');
|
||||
await this.renderDetail(tag);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.panelEl.classList.remove('open');
|
||||
this.setState({ selectedTag: null });
|
||||
}
|
||||
|
||||
render(): void {
|
||||
const state = this.getState();
|
||||
if (state.selectedTag) {
|
||||
this.renderDetail(state.selectedTag);
|
||||
}
|
||||
}
|
||||
|
||||
private async renderDetail(tag: Tag): Promise<void> {
|
||||
const state = this.getState();
|
||||
const img = getFullImg(tag);
|
||||
const name = getName(tag, state.lang);
|
||||
|
||||
this.panelEl.innerHTML = `
|
||||
<div class="detail-header">
|
||||
${img
|
||||
? `<img class="detail-img" src="${img}" alt="${tag.ref}">`
|
||||
: `<div class="detail-placeholder">${tag.ref?.slice(0, 2) || 'T'}</div>`
|
||||
}
|
||||
<button class="detail-close">×</button>
|
||||
</div>
|
||||
<div class="detail-body">
|
||||
<div class="detail-ref">${tag.ref || ''}</div>
|
||||
<div class="detail-mrf" data-mrf="${tag.mrf}">${tag.mrf}</div>
|
||||
<div class="detail-name">${name}</div>
|
||||
<div class="detail-desc">${tag.txt || tag.alias || ''}</div>
|
||||
<div id="children-section" class="detail-section" style="display:none">
|
||||
<h4>Hijos</h4>
|
||||
<div id="children-list" class="chip-list"></div>
|
||||
</div>
|
||||
<div id="related-section" class="detail-section" style="display:none">
|
||||
<h4>Relacionados</h4>
|
||||
<div id="related-list" class="chip-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.bindDetailEvents();
|
||||
await this.loadRelations(tag.mrf);
|
||||
}
|
||||
|
||||
private bindDetailEvents(): void {
|
||||
const closeBtn = this.panelEl.querySelector('.detail-close');
|
||||
closeBtn?.addEventListener('click', () => this.close());
|
||||
|
||||
const mrfEl = this.panelEl.querySelector('.detail-mrf');
|
||||
mrfEl?.addEventListener('click', () => {
|
||||
const mrf = (mrfEl as HTMLElement).dataset.mrf;
|
||||
if (mrf) copyMrf(mrf);
|
||||
});
|
||||
|
||||
delegateEvent<MouseEvent>(this.panelEl, '.tag-chip', 'click', (_, target) => {
|
||||
const mrf = target.dataset.mrf;
|
||||
if (mrf) this.showDetail(mrf);
|
||||
});
|
||||
}
|
||||
|
||||
private async loadRelations(mrf: string): Promise<void> {
|
||||
const [children, related] = await Promise.all([
|
||||
fetchChildren(mrf),
|
||||
fetchRelated(mrf)
|
||||
]);
|
||||
|
||||
const childrenSection = this.panelEl.querySelector('#children-section') as HTMLElement;
|
||||
const childrenList = this.panelEl.querySelector('#children-list') as HTMLElement;
|
||||
if (children.length > 0) {
|
||||
childrenSection.style.display = 'block';
|
||||
childrenList.innerHTML = children.map(c => {
|
||||
const label = c.name_es || c.alias || c.ref || c.mrf.slice(0, 8);
|
||||
return `<span class="tag-chip" data-mrf="${c.mrf}">${label}</span>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
const relatedSection = this.panelEl.querySelector('#related-section') as HTMLElement;
|
||||
const relatedList = this.panelEl.querySelector('#related-list') as HTMLElement;
|
||||
if (related.length > 0) {
|
||||
relatedSection.style.display = 'block';
|
||||
relatedList.innerHTML = related.map(r => {
|
||||
const label = r.name_es || r.alias || r.ref || r.mrf.slice(0, 8);
|
||||
return `<span class="tag-chip" data-mrf="${r.mrf}" title="${r.edge_type}">${label}</span>`;
|
||||
}).join('');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user