Skip to main content
Glama
DatabaseTable.svelte13.4 kB
<script> import { createEventDispatcher } from 'svelte'; export let tableName = ''; export let data = []; export let columns = []; const dispatch = createEventDispatcher(); let editingItem = null; let isNewItem = false; let showAddForm = false; let viewingItem = null; function startEdit(item) { editingItem = { ...item }; isNewItem = false; showAddForm = false; } function startNew() { editingItem = {}; isNewItem = true; showAddForm = true; viewingItem = null; } function cancelEdit() { editingItem = null; isNewItem = false; showAddForm = false; } function viewItem(item) { viewingItem = item; editingItem = null; showAddForm = false; } function closeView() { viewingItem = null; } function saveItem() { if (editingItem) { dispatch('save', { table: tableName, item: editingItem, isNew: isNewItem }); cancelEdit(); } } function deleteItem(id) { dispatch('delete', { table: tableName, id }); } function formatValue(value, column) { if (value === null || value === undefined) return '-'; switch (column.type) { case 'datetime': return new Date(value).toLocaleString('ko-KR'); case 'date': return new Date(value).toLocaleDateString('ko-KR'); case 'textarea': return value.length > 50 ? value.substring(0, 50) + '...' : value; case 'json-array': if (Array.isArray(value)) { return `[${value.length}개 항목]`; } try { const arr = JSON.parse(value); return Array.isArray(arr) ? `[${arr.length}개 항목]` : value; } catch { return value; } case 'json': if (typeof value === 'object' && value !== null) { return JSON.stringify(value).substring(0, 100) + '...'; } return value; default: return value; } } function parseJsonArray(value) { if (!value) return []; try { const parsed = JSON.parse(value); return Array.isArray(parsed) ? parsed : []; } catch { return []; } } function handleInputChange(column, value) { if (column.type === 'number') { editingItem[column.name] = value === '' ? null : Number(value); } else { editingItem[column.name] = value; } } function updateJsonArrayItem(fieldName, index, value) { const currentArray = parseJsonArray(editingItem[fieldName] || '[]'); currentArray[index] = value; editingItem[fieldName] = JSON.stringify(currentArray); } function addJsonArrayItem(fieldName) { const currentArray = parseJsonArray(editingItem[fieldName] || '[]'); currentArray.push(''); editingItem[fieldName] = JSON.stringify(currentArray); } function removeJsonArrayItem(fieldName, index) { const currentArray = parseJsonArray(editingItem[fieldName] || '[]'); currentArray.splice(index, 1); editingItem[fieldName] = JSON.stringify(currentArray); } </script> <div class="space-y-4"> <!-- Add New Button --> <div class="flex justify-between items-center"> <h3 class="text-lg font-semibold text-gray-900"> {tableName.charAt(0).toUpperCase() + tableName.slice(1)} 테이블 </h3> <button on:click={startNew} class="btn btn-primary"> + 새 항목 추가 </button> </div> <!-- Table --> <div class="card overflow-hidden"> <div class="overflow-x-auto"> <table class="min-w-full divide-y divide-gray-200"> <thead class="bg-gray-50"> <tr> {#each columns as column} <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> {column.label} </th> {/each} <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"> 작업 </th> </tr> </thead> <tbody class="bg-white divide-y divide-gray-200"> {#if showAddForm} <!-- Add Form Row --> <tr class="bg-blue-50"> {#each columns as column} <td class="px-6 py-4 whitespace-nowrap text-sm"> {#if column.readonly} <span class="text-gray-500">자동 생성</span> {:else if column.type === 'select'} <select class="form-select text-sm" bind:value={editingItem[column.name]} > <option value="">선택하세요</option> {#each column.options as option} <option value={option}>{option}</option> {/each} </select> {:else if column.type === 'textarea'} <textarea class="form-textarea text-sm" rows="3" bind:value={editingItem[column.name]} ></textarea> {:else if column.type === 'number'} <input class="form-input text-sm" type="number" bind:value={editingItem[column.name]} > {:else if column.type === 'date'} <input class="form-input text-sm" type="date" bind:value={editingItem[column.name]} > {:else if column.type === 'json-array'} <div class="space-y-2"> {#each parseJsonArray(editingItem[column.name] || '[]') as item, index} <div class="flex items-center space-x-2"> <input class="form-input text-sm flex-1" type="text" value={item} on:input={(e) => updateJsonArrayItem(column.name, index, e.target.value)} > <button type="button" class="text-red-600 hover:text-red-800" on:click={() => removeJsonArrayItem(column.name, index)} > 삭제 </button> </div> {/each} <button type="button" class="text-blue-600 hover:text-blue-800 text-sm" on:click={() => addJsonArrayItem(column.name)} > + 항목 추가 </button> </div> {:else} <input class="form-input text-sm" type="text" bind:value={editingItem[column.name]} > {/if} </td> {/each} <td class="px-6 py-4 whitespace-nowrap text-right text-sm space-x-2"> <button on:click={saveItem} class="text-green-600 hover:text-green-900">저장</button> <button on:click={cancelEdit} class="text-gray-600 hover:text-gray-900">취소</button> </td> </tr> {/if} {#each data as item (item.id)} <tr class={editingItem && editingItem.id === item.id ? 'bg-yellow-50' : ''}> {#each columns as column} <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"> {#if editingItem && editingItem.id === item.id} {#if column.readonly} <span class="text-gray-500">{formatValue(item[column.name], column)}</span> {:else if column.type === 'select'} <select class="form-select text-sm" value={editingItem[column.name]} on:change={(e) => handleInputChange(column, e.target.value)} > <option value="">선택하세요</option> {#each column.options as option} <option value={option}>{option}</option> {/each} </select> {:else if column.type === 'textarea'} <textarea class="form-textarea text-sm" rows="3" value={editingItem[column.name] || ''} on:input={(e) => handleInputChange(column, e.target.value)} ></textarea> {:else if column.type === 'json-array'} <div class="space-y-1"> {#each parseJsonArray(editingItem[column.name] || '[]') as item, index} <div class="flex items-center space-x-1"> <input class="form-input text-xs flex-1" type="text" value={item} on:input={(e) => updateJsonArrayItem(column.name, index, e.target.value)} > <button type="button" class="text-red-500 hover:text-red-700 text-xs" on:click={() => removeJsonArrayItem(column.name, index)} > × </button> </div> {/each} <button type="button" class="text-blue-500 hover:text-blue-700 text-xs" on:click={() => addJsonArrayItem(column.name)} > + 추가 </button> </div> {:else} <input class="form-input text-sm" type={column.type === 'number' ? 'number' : column.type === 'date' ? 'date' : 'text'} value={editingItem[column.name] || ''} on:input={(e) => handleInputChange(column, e.target.value)} > {/if} {:else} {formatValue(item[column.name], column)} {/if} </td> {/each} <td class="px-6 py-4 whitespace-nowrap text-right text-sm space-x-2"> {#if editingItem && editingItem.id === item.id} <button on:click={saveItem} class="text-green-600 hover:text-green-900">저장</button> <button on:click={cancelEdit} class="text-gray-600 hover:text-gray-900">취소</button> {:else} <button on:click={() => viewItem(item)} class="text-indigo-600 hover:text-indigo-900">상세</button> <button on:click={() => startEdit(item)} class="text-blue-600 hover:text-blue-900">수정</button> <button on:click={() => deleteItem(item.id)} class="text-red-600 hover:text-red-900">삭제</button> {/if} </td> </tr> {/each} </tbody> </table> </div> {#if data.length === 0} <div class="text-center py-12"> <div class="text-gray-500">데이터가 없습니다</div> </div> {/if} </div> <!-- Table Info --> <div class="text-sm text-gray-500"> 총 {data.length}개 항목 </div> </div> <!-- Detail View Modal --> {#if viewingItem} <div class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"> <div class="relative top-20 mx-auto p-5 border w-11/12 max-w-4xl shadow-lg rounded-md bg-white"> <div class="mt-3"> <!-- Header --> <div class="flex items-center justify-between mb-6"> <h3 class="text-lg font-semibold text-gray-900"> {tableName.charAt(0).toUpperCase() + tableName.slice(1)} 상세 보기 </h3> <button on:click={closeView} class="text-gray-400 hover:text-gray-600"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> </div> <!-- Content --> <div class="space-y-6 max-h-96 overflow-y-auto"> <!-- 원본 JSON 데이터 섹션 --> {#if viewingItem.document_content} <div class="mb-6 p-4 bg-blue-50 border border-blue-200 rounded-lg"> <h4 class="text-lg font-semibold text-blue-900 mb-3">📄 원본 JSON 문서 내용</h4> <pre class="whitespace-pre-wrap text-sm bg-white p-4 rounded border max-h-80 overflow-y-auto font-mono border-2 border-blue-300">{JSON.stringify(viewingItem.document_content, null, 2)}</pre> </div> {/if} <!-- 기본 필드들 --> <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> {#each columns as column} <div class="space-y-2"> <label class="block text-sm font-medium text-gray-700"> {column.label} </label> <div class="p-3 bg-gray-50 rounded-md"> {#if column.type === 'json-array'} <div class="space-y-1"> {#each parseJsonArray(viewingItem[column.name]) as item, index} <div class="flex items-center space-x-2"> <span class="text-xs text-gray-500">{index + 1}.</span> <span class="text-sm">{typeof item === 'object' ? JSON.stringify(item) : item}</span> </div> {/each} {#if parseJsonArray(viewingItem[column.name]).length === 0} <span class="text-sm text-gray-400">항목이 없습니다</span> {/if} </div> {:else if column.type === 'json'} <div class="text-sm"> {#if typeof viewingItem[column.name] === 'object' && viewingItem[column.name] !== null} <pre class="whitespace-pre-wrap text-xs bg-white p-2 rounded border max-h-40 overflow-y-auto">{JSON.stringify(viewingItem[column.name], null, 2)}</pre> {:else} {viewingItem[column.name] || '-'} {/if} </div> {:else if column.type === 'textarea'} <div class="text-sm whitespace-pre-wrap max-h-32 overflow-y-auto"> {viewingItem[column.name] || '-'} </div> {:else} <div class="text-sm"> {formatValue(viewingItem[column.name], column)} </div> {/if} </div> </div> {/each} </div> </div> <!-- Footer --> <div class="flex justify-end mt-6 pt-6 border-t"> <button on:click={() => startEdit(viewingItem)} class="mr-3 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"> 수정하기 </button> <button on:click={closeView} class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400"> 닫기 </button> </div> </div> </div> </div> {/if}

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/foswmine/workflow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server