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:
@@ -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">
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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}`,
|
||||||
{},
|
{},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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[]> =>
|
||||||
|
|||||||
@@ -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) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user