feat: initial database ui

This commit is contained in:
arashsheyda
2023-04-21 12:17:38 +03:00
parent c8b8583ae9
commit 711d15926f
2 changed files with 217 additions and 0 deletions

View File

@ -0,0 +1,162 @@
<script lang="ts" setup>
const props = defineProps({
collection: {
type: String,
required: true,
},
})
const documents = computedAsync(async () => {
return await rpc.listDocuments(props.collection)
})
const fields = computed(() => {
if (documents.value && documents.value.length > 0)
return Object.keys(documents.value[0])
})
const editing = ref(false)
const selectedDocument = ref()
function editDocument(document: any) {
if (editing.value)
return
editing.value = true
selectedDocument.value = { ...document }
}
async function saveDocument() {
// TODO: validate & show errors
await rpc.updateDocument(props.collection, selectedDocument.value)
editing.value = false
selectedDocument.value = undefined
documents.value = await rpc.listDocuments(props.collection)
}
function discardEditing() {
editing.value = false
selectedDocument.value = null
}
async function deleteDocument(document: any) {
rpc.deleteDocument(props.collection, document._id)
documents.value = await rpc.listDocuments(props.collection)
}
</script>
<template>
<div w-full h-full p4>
<template v-if="fields?.length">
<div flex justify-between>
<div flex-auto />
<NButton icon="carbon:add" n="green" @click="drawer = !drawer">
Add Document
</NButton>
</div>
<table w-full mt4 :class="{ 'editing-mode': editing }">
<thead>
<tr>
<th v-for="field of fields" :key="field" text-start>
{{ field }}
</th>
<th text-center>
Actions
</th>
</tr>
</thead>
<tbody>
<tr v-for="document in documents" :key="document._id" :class="{ isEditing: editing && selectedDocument._id === document._id }">
<td v-for="field of fields" :key="field" hover-bg-green hover-bg-opacity-5 hover-text-green cursor-pointer @dblclick="editDocument(document)">
<template v-if="editing && selectedDocument._id === document._id">
<input v-model="selectedDocument[field]">
</template>
<span v-else>
{{ document[field] }}
</span>
</td>
<td flex justify-center gap2 class="actions">
<template v-if="editing && selectedDocument._id === document._id">
<NIconButton icon="carbon-save" @click="saveDocument" />
<NIconButton icon="carbon-close" @click="discardEditing" />
</template>
<template v-else>
<NIconButton icon="carbon-edit" @click="editDocument(document)" />
<NIconButton icon="carbon-delete" @click="deleteDocument(document)" />
</template>
</td>
</tr>
</tbody>
</table>
</template>
<div v-else flex justify-center items-center h-full text-2xl>
<NIcon icon="carbon-document" mr1 />
No documents found
</div>
</div>
</template>
<style lang="scss">
.actions .n-icon {
margin: 0;
}
table {
table-layout: fixed;
tr {
width: 100%;
td, th {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
input {
border: none;
background: transparent;
color: rgba(255, 255, 255, 0.7);
width: 100%;
&:focus {
outline: none;
}
}
}
th {
border-right: 1px solid #272727;
border-left: 1px solid #272727;
border-top: 1px solid #272727;
padding: 5px 10px;
}
td {
border: 1px solid #272727;
padding: 5px 10px;
color: rgba(255, 255, 255, 0.7);
&:hover {
color: #fff;
}
}
.editing-mode {
tr {
&:not(.isEditing) {
opacity: 0.3;
position: relative;
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
opacity: 0.3;
}
}
&.isEditing {
opacity: 1;
}
}
}
</style>

55
client/pages/index.vue Normal file
View File

@ -0,0 +1,55 @@
<script lang="ts" setup>
const route = useRoute()
const selectedCollection = ref()
// TODO: check connection
const connected = ref(true)
const drawer = ref(false)
const search = ref('')
const collections = computedAsync(async () => {
return await rpc.listCollections()
})
const filtered = computed(() => {
if (!search.value)
return collections.value
return collections.value.filter((c: any) => c.name.toLowerCase().includes(search.value.toLowerCase()))
})
onMounted(() => {
if (route.query.table)
selectedCollection.value = route.query.table
})
</script>
<template>
<div h-full>
<PanelLeftRight :min-left="13" :max-left="20">
<template #left>
<div py1 px2 border="r base">
<div flex items-center p2>
<!-- TODO: -->
<NIconButton w-full mb2 icon="carbon-reset" title="Refresh" />
<NIconButton w-full mb2 icon="carbon-data-base" title="Connection Name" :class="connected ? 'text-green-5' : 'text-orange-5'" />
<NIconButton w-full mb2 icon="carbon-add" title="Create Table" @click="drawer = !drawer" />
</div>
<NTextInput v-model="search" w-full p2 mb2 :placeholder="`${collections?.length ?? '-'} collection in total`" icon="carbon-search" />
<div grid gird-cols-1 my2 mx1>
<NuxtLink v-for="table in filtered" :key="table.name" :to="{ name: 'index', query: { table: table.name } }" flex justify-between p2 my1 hover-bg-green hover-bg-opacity-5 hover-text-green rounded-lg :class="{ 'bg-green bg-opacity-5 text-green': selectedCollection === table.name }" @click="selectedCollection = table.name">
<span>
<NIcon icon="carbon-db2-database" />
{{ table.name }}
</span>
<!-- TODO: -->
<NIconButton icon="carbon-overflow-menu-horizontal" />
</NuxtLink>
</div>
</div>
</template>
<template #right>
<DatabaseDetail v-if="selectedCollection" :collection="selectedCollection" />
</template>
</PanelLeftRight>
</div>
</template>