2025-12-22 16:54:19 +07:00

195 lines
4.5 KiB
JavaScript

/**
* CLQMS V2 Frontend - Alpine.js Components
*/
document.addEventListener('alpine:init', () => {
/**
* Toast Store
*/
Alpine.store('toast', {
messages: [],
show(message, type = 'info', duration = 4000) {
const id = Date.now();
this.messages.push({ id, message, type });
setTimeout(() => this.dismiss(id), duration);
},
dismiss(id) {
this.messages = this.messages.filter(m => m.id !== id);
},
success(msg) { this.show(msg, 'success'); },
error(msg) { this.show(msg, 'error', 6000); },
info(msg) { this.show(msg, 'info'); }
});
/**
* API Response Store
*/
Alpine.store('apiResponse', {
hasResponse: false,
status: '',
statusClass: '',
time: 0,
body: '',
set(status, body, time) {
this.hasResponse = true;
this.status = status;
this.statusClass = status >= 200 && status < 300 ? 'badge-success' : 'badge-error';
this.time = time;
this.body = typeof body === 'string' ? body : JSON.stringify(body, null, 2);
}
});
/**
* API Tester Component
*/
Alpine.data('apiTester', () => ({
method: 'GET',
url: '/api/patient',
body: '{\n \n}',
loading: false,
setEndpoint(method, url) {
this.method = method;
this.url = url;
},
async sendRequest() {
this.loading = true;
const startTime = performance.now();
try {
const options = {
method: this.method,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'include'
};
if (this.method === 'POST' || this.method === 'PATCH') {
try {
options.body = this.body;
JSON.parse(this.body); // Validate JSON
} catch (e) {
Alpine.store('toast').error('Invalid JSON body');
this.loading = false;
return;
}
}
const response = await fetch(this.url, options);
const endTime = performance.now();
let data;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
data = await response.json();
} else {
data = await response.text();
}
Alpine.store('apiResponse').set(
response.status,
data,
Math.round(endTime - startTime)
);
} catch (err) {
Alpine.store('toast').error('Request failed: ' + err.message);
} finally {
this.loading = false;
}
}
}));
/**
* DB Browser Component
*/
Alpine.data('dbBrowser', () => ({
tables: [],
loadingTables: true,
selectedTable: null,
tableData: null,
loadingData: false,
init() {
this.loadTables();
},
async loadTables() {
this.loadingTables = true;
try {
const res = await fetch('/v2/api/tables', { credentials: 'include' });
const data = await res.json();
this.tables = data.tables || [];
} catch (e) {
Alpine.store('toast').error('Failed to load tables');
} finally {
this.loadingTables = false;
}
},
async selectTable(table) {
this.selectedTable = table;
this.loadingData = true;
this.tableData = null;
try {
const res = await fetch(`/v2/api/table/${table}`, { credentials: 'include' });
this.tableData = await res.json();
} catch (e) {
Alpine.store('toast').error('Failed to load table data');
} finally {
this.loadingData = false;
}
}
}));
/**
* Logs Viewer Component
*/
Alpine.data('logsViewer', () => ({
logs: [],
loading: true,
expandedLogs: [],
init() {
this.loadLogs();
},
async loadLogs() {
this.loading = true;
try {
const res = await fetch('/v2/api/logs', { credentials: 'include' });
const data = await res.json();
this.logs = data.logs || [];
} catch (e) {
Alpine.store('toast').error('Failed to load logs');
} finally {
this.loading = false;
}
},
toggleLog(name) {
if (this.expandedLogs.includes(name)) {
this.expandedLogs = this.expandedLogs.filter(n => n !== name);
} else {
this.expandedLogs.push(name);
}
},
formatSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
}));
});