Add multi-schema support and per-base libraries

- Swap MST/BCK and MTH/ATC button positions
- Add schema support for different PostgreSQL schemas:
  - secretaria_clara: atc, mst, bck
  - production_alfred: mth
  - mail_manager: mail
  - context_manager: chat
- Libraries now load per-base (library_{base})
- Add new base types: key, mindlink
- Update FetchOptions to use schema instead of headers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
ARCHITECT
2026-01-13 00:43:54 +00:00
parent 030b2a5312
commit a7ab3bab7d
7 changed files with 101 additions and 13 deletions

View File

@@ -22,6 +22,7 @@
<button class="btn btn-sm" id="btn-get">GET</button> <button class="btn btn-sm" id="btn-get">GET</button>
</div> </div>
<div class="topbar-center"> <div class="topbar-center">
<!-- Taxonomía -->
<div class="base-buttons"> <div class="base-buttons">
<button class="base-btn active" data-base="hst">HST</button> <button class="base-btn active" data-base="hst">HST</button>
<button class="base-btn" data-base="flg">FLG</button> <button class="base-btn" data-base="flg">FLG</button>
@@ -29,6 +30,26 @@
<button class="base-btn" data-base="loc">LOC</button> <button class="base-btn" data-base="loc">LOC</button>
<button class="base-btn" data-base="ply">PLY</button> <button class="base-btn" data-base="ply">PLY</button>
</div> </div>
<!-- Maestros -->
<div class="base-buttons">
<button class="base-btn" data-base="mst">MST</button>
<button class="base-btn" data-base="bck">BCK</button>
</div>
<!-- Registro -->
<div class="base-buttons">
<button class="base-btn" data-base="mth">MTH</button>
<button class="base-btn" data-base="atc">ATC</button>
</div>
<!-- Comunicación -->
<div class="base-buttons">
<button class="base-btn" data-base="mail">MAIL</button>
<button class="base-btn" data-base="chat">CHAT</button>
</div>
<!-- Servicios -->
<div class="base-buttons">
<button class="base-btn" data-base="key">KEY</button>
<button class="base-btn" data-base="mindlink">MIND</button>
</div>
</div> </div>
<div class="topbar-right"> <div class="topbar-right">
<div class="search-box"> <div class="search-box">

View File

@@ -3,17 +3,22 @@ import { API_BASE } from '@/config/index.ts';
interface FetchOptions { interface FetchOptions {
method?: 'GET' | 'POST'; method?: 'GET' | 'POST';
body?: Record<string, unknown>; body?: Record<string, unknown>;
schema?: string; // PostgREST Accept-Profile header
} }
export async function apiClient<T>( export async function apiClient<T>(
endpoint: string, endpoint: string,
options: FetchOptions = {} options: FetchOptions = {}
): Promise<T> { ): Promise<T> {
const { method = 'GET', body } = options; const { method = 'GET', body, schema } = options;
const headers: Record<string, string> = {};
if (body) headers['Content-Type'] = 'application/json';
if (schema) headers['Accept-Profile'] = schema;
const config: RequestInit = { const config: RequestInit = {
method, method,
headers: body ? { 'Content-Type': 'application/json' } : undefined, headers: Object.keys(headers).length > 0 ? headers : undefined,
body: body ? JSON.stringify(body) : undefined, body: body ? JSON.stringify(body) : undefined,
}; };

View File

@@ -1,12 +1,24 @@
import { apiClientSafe } from './client.ts'; import { apiClientSafe } from './client.ts';
import type { Library } from '@/types/index.ts'; import type { Library, BaseType } from '@/types/index.ts';
export const fetchLibraries = (): Promise<Library[]> => // Base types that have library tables (public schema taxonomy tables)
apiClientSafe<Library[]>('/api_library_list', {}, []); const LIBRARY_BASES = new Set(['hst', 'flg', 'itm', 'loc', 'ply']);
export const fetchLibraryMembers = async (mrf: string): Promise<string[]> => { export const fetchLibraries = (base: BaseType): Promise<Library[]> => {
// Only public schema taxonomy tables have libraries
if (!LIBRARY_BASES.has(base)) {
return Promise.resolve([]);
}
// Try base-specific endpoint, fallback to generic
return apiClientSafe<Library[]>(`/api_library_list?base=eq.${base}`, {}, []);
};
export const fetchLibraryMembers = async (mrf: string, base: BaseType): Promise<string[]> => {
if (!LIBRARY_BASES.has(base)) {
return [];
}
const data = await apiClientSafe<Array<{ mrf_tag: string }>>( const data = await apiClientSafe<Array<{ mrf_tag: string }>>(
`/library_hst?mrf_library=eq.${mrf}`, `/library_${base}?mrf_library=eq.${mrf}`,
{}, {},
[] []
); );

View File

@@ -1,8 +1,52 @@
import { apiClientSafe } from './client.ts'; import { apiClientSafe } from './client.ts';
import type { Tag, ChildTag, RelatedTag, BaseType } from '@/types/index.ts'; import type { Tag, ChildTag, RelatedTag, BaseType } from '@/types/index.ts';
export const fetchTags = (base: BaseType): Promise<Tag[]> => // Schema mapping by base type
apiClientSafe<Tag[]>(`/${base}?order=ref.asc`, {}, []); // - public (default): hst, flg, itm, loc, ply
// - secretaria_clara: atc, mst, bck
// - production_alfred: mth
// - mail_manager: mail (table: clara_registros)
// - context_manager: chat (table: messages)
interface SchemaTableConfig {
schema: string | null;
table: string;
}
const getSchemaAndTable = (base: BaseType): SchemaTableConfig => {
switch (base) {
// secretaria_clara schema
case 'atc':
case 'mst':
case 'bck':
return { schema: 'secretaria_clara', table: base };
// production_alfred schema
case 'mth':
return { schema: 'production_alfred', table: base };
// mail_manager schema
case 'mail':
return { schema: 'mail_manager', table: 'clara_registros' };
// context_manager schema
case 'chat':
return { schema: 'context_manager', table: 'messages' };
// public schema (default) - hst, flg, itm, loc, ply
default:
return { schema: null, table: base };
}
};
export const fetchTags = (base: BaseType): Promise<Tag[]> => {
const { schema, table } = getSchemaAndTable(base);
return apiClientSafe<Tag[]>(
`/${table}?order=ref.asc`,
schema ? { schema } : {},
[]
);
};
// Fetch HST tags for group name resolution (set_hst points to hst tags) // Fetch HST tags for group name resolution (set_hst points to hst tags)
export const fetchHstTags = (): Promise<Tag[]> => export const fetchHstTags = (): Promise<Tag[]> =>

View File

@@ -39,7 +39,7 @@ class App {
fetchTags(state.base), fetchTags(state.base),
fetchHstTags(), // Always load HST for group name resolution fetchHstTags(), // Always load HST for group name resolution
fetchGroups(), fetchGroups(),
fetchLibraries() fetchLibraries(state.base) // Load libraries for current base
]); ]);
store.setState({ tags, hstTags, groups, libraries }); store.setState({ tags, hstTags, groups, libraries });
@@ -153,11 +153,12 @@ class App {
delegateEvent<MouseEvent>(container, '.lib-icon', 'click', async (_, target) => { delegateEvent<MouseEvent>(container, '.lib-icon', 'click', async (_, target) => {
const library = target.dataset.lib || 'all'; const library = target.dataset.lib || 'all';
const currentBase = store.getState().base;
if (library === 'all') { if (library === 'all') {
store.setState({ library: 'all', libraryMembers: new Set() }); store.setState({ library: 'all', libraryMembers: new Set() });
} else { } else {
const members = await fetchLibraryMembers(library); const members = await fetchLibraryMembers(library, currentBase);
store.setState({ library, libraryMembers: new Set(members) }); store.setState({ library, libraryMembers: new Set(members) });
} }

View File

@@ -39,7 +39,7 @@ body {
gap: 12px; gap: 12px;
} }
.topbar-left { display: flex; align-items: center; gap: 10px; } .topbar-left { display: flex; align-items: center; gap: 10px; }
.topbar-center { flex: 1; display: flex; justify-content: center; } .topbar-center { flex: 1; display: flex; justify-content: center; gap: 16px; }
.topbar-right { display: flex; align-items: center; gap: 10px; } .topbar-right { display: flex; align-items: center; gap: 10px; }
.logo { font-weight: 700; font-size: 1.2em; color: var(--accent); letter-spacing: 1px; } .logo { font-weight: 700; font-size: 1.2em; color: var(--accent); letter-spacing: 1px; }

View File

@@ -2,7 +2,12 @@ import type { Tag, Group, Library } from './tag.ts';
import type { GraphEdge, TreeEdge, CategoryKey, EdgeType } from './graph.ts'; import type { GraphEdge, TreeEdge, CategoryKey, EdgeType } from './graph.ts';
export type ViewType = 'grid' | 'tree' | 'graph'; export type ViewType = 'grid' | 'tree' | 'graph';
export type BaseType = 'hst' | 'flg' | 'itm' | 'loc' | 'ply'; export type BaseType =
| 'hst' | 'flg' | 'itm' | 'loc' | 'ply' // Taxonomía (public)
| 'mth' | 'atc' // Registro (secretaria_clara, production_alfred)
| 'mst' | 'bck' // Maestros (secretaria_clara)
| 'mail' | 'chat' // Comunicación (mail_manager, context_manager)
| 'key' | 'mindlink'; // Servicios
export type LangType = 'es' | 'en' | 'ch'; export type LangType = 'es' | 'en' | 'ch';
export interface GraphFilters { export interface GraphFilters {