mirror of https://github.com/nocodb/nocodb
18 changed files with 487 additions and 70 deletions
@ -0,0 +1,31 @@
|
||||
<template> |
||||
<div> |
||||
<TabMenu :model="tabItems" v-model:activeIndex="activeIndex"/> |
||||
<template v-if="tabItems && tabItems[activeIndex]"> |
||||
<TabsSmartsheet :tab-meta="tabs[activeIndex]" :key="tabs[activeIndex].id"/> |
||||
</template> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
|
||||
import {useTabs} from "~/composables/tabs"; |
||||
|
||||
const {tabs} = useTabs() |
||||
const activeIndex = ref(0) |
||||
|
||||
const tabItems = computed(() => { |
||||
return tabs.value.map(tab => { |
||||
return { |
||||
label: tab.title, |
||||
// icon: tab.icon, |
||||
closable: true |
||||
} |
||||
}) |
||||
}) |
||||
|
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -0,0 +1,22 @@
|
||||
<template> |
||||
<div> |
||||
|
||||
<div v-for="table in tables" class="p-2 text-sm" |
||||
@click="addTab({type:'table',title:table.title, id:table.id})"> |
||||
{{ table.title }} |
||||
</div> |
||||
|
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import {useProject} from "~/composables/project"; |
||||
import {useTabs} from "~/composables/tabs"; |
||||
|
||||
const {tables} = useProject() |
||||
const {addTab} = useTabs() |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -0,0 +1,56 @@
|
||||
<template> |
||||
<div> |
||||
|
||||
<div class="card"> |
||||
<DataTable :value="rows" responsiveLayout="scroll"> |
||||
|
||||
<Column v-for="col in meta.columns" :key="col.id" :field="col.title" :header="col.title"> |
||||
<template v-if="col.uidt === 'LinkToAnotherRecord'" #body="{data:{[col.title]:d}}"> |
||||
{{ d&& (Array.isArray(d) ? d : [d]).map(c1 => c1[Object.keys(c1)[1]]).join(', ') }} |
||||
</template> |
||||
</Column> |
||||
</DataTable> |
||||
</div> |
||||
|
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import {useNuxtApp} from "#app"; |
||||
import {Api} from "nocodb-sdk"; |
||||
import {useUser} from "~/composables/user"; |
||||
|
||||
const {tabMeta, meta} = defineProps({ |
||||
tabMeta: Object, |
||||
meta: Object |
||||
}) |
||||
|
||||
const {project} = useProject() |
||||
const {user} = useUser() |
||||
const rows = ref() |
||||
|
||||
const {$api}: { $api: Api<any> } = useNuxtApp() as any |
||||
|
||||
|
||||
const loadData = async () => { |
||||
const response = await $api.dbTableRow.list('noco', |
||||
project.value.id, |
||||
meta.id, {}, { |
||||
headers: { |
||||
'xc-auth': user.token |
||||
} |
||||
}) |
||||
|
||||
rows.value = response.list |
||||
} |
||||
|
||||
onMounted(async () => { |
||||
await loadData() |
||||
}) |
||||
|
||||
|
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -0,0 +1,36 @@
|
||||
<template> |
||||
<div> |
||||
<template v-if="meta && tabMeta"> |
||||
<SmartsheetGrid :meta="meta" :tabMeta="tabMeta"></SmartsheetGrid> |
||||
</template> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import {useMetas} from "~/composables/metas"; |
||||
import {computed, onMounted, watch} from 'vue' |
||||
|
||||
const {tabMeta} = defineProps({ |
||||
tabMeta: Object |
||||
}) |
||||
|
||||
const {getMeta, metas} = useMetas() |
||||
|
||||
const meta = computed(() => { |
||||
return metas.value?.[tabMeta?.id] |
||||
}) |
||||
|
||||
onMounted(async () => { |
||||
await getMeta(tabMeta?.id) |
||||
}) |
||||
|
||||
watch(() => tabMeta && tabMeta?.id, async (newVal, oldVal) => { |
||||
if (newVal !== oldVal) { |
||||
await getMeta(newVal) |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -0,0 +1,41 @@
|
||||
import {useNuxtApp, useState} from "#app"; |
||||
import {Api, TableType} from "nocodb-sdk"; |
||||
import {useUser} from "~/composables/user"; |
||||
import {useProject} from "~/composables/project"; |
||||
|
||||
|
||||
export const useMetas = () => { |
||||
const {$api}: { $api: Api<any> } = useNuxtApp() as any |
||||
const {user} = useUser() |
||||
const {tables} = useProject() |
||||
|
||||
const metas = useState<{ [idOrTitle: string]: TableType | any }>('metas', () => ({})) |
||||
|
||||
const getMeta = async (tableIdOrTitle: string, force = false) => { |
||||
if (!force && metas[tableIdOrTitle]) { |
||||
return metas[tableIdOrTitle] |
||||
} |
||||
|
||||
const modelId = (tables.value.find(t => t.title === tableIdOrTitle || t.id === tableIdOrTitle) || {}).id |
||||
if (!modelId) { |
||||
console.warn(`Table '${tableIdOrTitle}' is not found in the table list`) |
||||
return |
||||
} |
||||
|
||||
const model = await $api.dbTable.read(modelId, { |
||||
headers: { |
||||
'xc-auth': user.token |
||||
} |
||||
}) |
||||
|
||||
metas.value = { |
||||
...metas.value, |
||||
[model.id]: model, |
||||
[model.title]: model |
||||
} |
||||
|
||||
return model |
||||
} |
||||
|
||||
return {getMeta, metas} |
||||
} |
@ -0,0 +1,36 @@
|
||||
import {useNuxtApp} from "#app"; |
||||
import {Api, TableType} from "nocodb-sdk"; |
||||
import {useUser} from "~/composables/user"; |
||||
|
||||
export const useProject = () => { |
||||
const {$api}: { $api: Api<any> } = useNuxtApp() as any |
||||
const {user} = useUser() |
||||
|
||||
const project = useState<{ id?: string, title?: string }>('project', null) |
||||
const tables = useState<Array<TableType>>('tables', null) |
||||
|
||||
const loadTables = async () => { |
||||
const tablesResponse = await $api.dbTable.list(project?.value?.id, {}, { |
||||
headers: { |
||||
'xc-auth': user.token |
||||
} |
||||
}) |
||||
|
||||
console.log(tablesResponse) |
||||
tables.value = tablesResponse.list |
||||
} |
||||
|
||||
const loadProject = async (projectId:string) => { |
||||
const projectResponse = await $api.project.read(projectId, { |
||||
headers: { |
||||
'xc-auth': user.token |
||||
} |
||||
}) |
||||
|
||||
console.log(projectResponse) |
||||
project.value = projectResponse |
||||
} |
||||
|
||||
|
||||
return {project, tables, loadProject, loadTables} |
||||
} |
@ -0,0 +1,17 @@
|
||||
import {useState} from "#app"; |
||||
|
||||
interface TabItem { |
||||
type: 'table' | 'view', |
||||
title: string, |
||||
id:string |
||||
} |
||||
|
||||
export const useTabs = () => { |
||||
const tabs = useState<Array<TabItem>>('tabs', () => []) |
||||
|
||||
const addTab = (tabMeta: TabItem) => { |
||||
tabs.value = [...(tabs.value || []), tabMeta] |
||||
} |
||||
|
||||
return {tabs, addTab} |
||||
} |
@ -1,9 +1,15 @@
|
||||
import {store} from 'nuxt3-store' |
||||
|
||||
export const user = store({ |
||||
export const useUser = () =>{ |
||||
const user = store({ |
||||
name: 'user', |
||||
type: 'localstorage', |
||||
value: {token: null}, |
||||
reactiveType: 'reactive', |
||||
version: '1.0.0' |
||||
}) |
||||
|
||||
const setToken = (token) => { user.token = token } |
||||
|
||||
return {user,setToken} |
||||
} |
||||
|
@ -0,0 +1,14 @@
|
||||
export async function extractSdkResponseErrorMsg(e:Error & {response:any}) { |
||||
if (!e || !e.response) { return e.message } |
||||
let msg |
||||
if (e.response.data instanceof Blob) { |
||||
try { |
||||
msg = JSON.parse(await e.response.data.text()).msg |
||||
} catch { |
||||
msg = 'Some internal error occurred' |
||||
} |
||||
} else { |
||||
msg = e.response.data.msg || 'Some internal error occurred' |
||||
} |
||||
return msg || 'Some error occurred' |
||||
} |
@ -0,0 +1,56 @@
|
||||
<template> |
||||
<div class="nc-container"> |
||||
<div class="nc-topbar shadow-2"> |
||||
</div> |
||||
<div class="nc-sidebar shadow-2 p-4 overflow-y-auto"> |
||||
<DashboardTreeView></DashboardTreeView> |
||||
</div> |
||||
<div class="nc-content p-4 overflow-auto"> |
||||
<DashboardTabView></DashboardTabView> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import {useNuxtApp} from "#app"; |
||||
import {useProject} from "~/composables/project"; |
||||
|
||||
const route = useRoute() |
||||
const {loadProject, loadTables} = useProject() |
||||
|
||||
|
||||
onMounted(async () => { |
||||
await loadProject(route.params.projectId as string) |
||||
await loadTables() |
||||
}) |
||||
|
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.nc-container { |
||||
.nc-topbar { |
||||
position: fixed; |
||||
top: 0; |
||||
left: 0; |
||||
height: 50px; |
||||
width: 100%; |
||||
|
||||
} |
||||
|
||||
.nc-sidebar { |
||||
position: fixed; |
||||
top: 50px; |
||||
left: 0; |
||||
height: calc(100% - 50px); |
||||
width: 250px; |
||||
} |
||||
|
||||
.nc-content { |
||||
position: fixed; |
||||
top: 50px; |
||||
left: 250px; |
||||
height: calc(100% - 50px); |
||||
width: calc(100% - 250px); |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,51 @@
|
||||
<template> |
||||
<div> |
||||
|
||||
<div class="grid"> |
||||
<div class="col-3 p-3" v-for="project in projects" :key="project.id"> |
||||
|
||||
<Card @click="navigateToDashboard(project)"> |
||||
<template #content> |
||||
<div class="text-center"> |
||||
<h3>{{ project.title }}</h3> |
||||
</div> |
||||
</template> |
||||
</Card> |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
|
||||
import {Api} from "nocodb-sdk"; |
||||
|
||||
const {$api, $router}= useNuxtApp() |
||||
const projects = ref() |
||||
const {user} = useUser() |
||||
|
||||
const loadProjects = async () => { |
||||
const projectsResponse = await $api.project.list({}, { |
||||
headers: { |
||||
'xc-auth': user.token |
||||
} |
||||
}) |
||||
projects.value = projectsResponse.list |
||||
} |
||||
|
||||
const navigateToDashboard = async (project) => { |
||||
await $router.push({ |
||||
path: '/dashboard/' + project.id |
||||
}) |
||||
} |
||||
|
||||
onMounted(async () => { |
||||
await loadProjects() |
||||
}) |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -1,72 +1,62 @@
|
||||
<template> |
||||
<v-cotainer> |
||||
<v-card max-width="500" class="pa-4 mx-auto mt-10"> |
||||
{{userStore}} |
||||
<v-form |
||||
ref="formType" |
||||
v-model="valid" |
||||
lazy-validation |
||||
> |
||||
<div> |
||||
<!-- Enter your work email --> |
||||
<v-text-field |
||||
v-model="form.email" |
||||
label="Email" |
||||
required |
||||
/> |
||||
<Card class="p-4 mx-auto mt-5" style="max-width: 500px"> |
||||
<template #content> |
||||
|
||||
<!-- Enter your password --> |
||||
<v-text-field |
||||
label="Password" |
||||
v-model="form.password" |
||||
name="input-10-2" |
||||
min="8" |
||||
/> |
||||
<Message class="" v-if="error" severity="error">{{ error }}</Message> |
||||
|
||||
<v-btn |
||||
class="mx-auto" |
||||
large |
||||
elevation-10 |
||||
:disabled="false" |
||||
<div class="p-float-label mt-5"> |
||||
<InputText id="email" type="text" v-model="form.email" style="width:100%"/> |
||||
<label for="email">Email</label> |
||||
</div> |
||||
|
||||
<!-- Enter your password --> |
||||
<div class="p-float-label mt-5"> |
||||
<InputText id="password" type="password" v-model="form.password" style="width:100%"/> |
||||
<label for="password">Password</label> |
||||
</div> |
||||
|
||||
<div class="text-center"> |
||||
<Button |
||||
class="mt-5" |
||||
@click="signIn" |
||||
> |
||||
<b>Sign In</b> |
||||
</v-btn> |
||||
|
||||
</v-form> |
||||
</v-card> |
||||
</v-cotainer> |
||||
</Button> |
||||
</div> |
||||
</template> |
||||
</Card> |
||||
</div> |
||||
|
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import {ref, reactive} from 'vue' |
||||
import {useUser} from "~/composables/user"; |
||||
import {extractSdkResponseErrorMsg} from "~/helpers/errorUtils"; |
||||
import {useNuxtApp} from "#app"; |
||||
|
||||
const {$api} = useNuxtApp() |
||||
const valid = ref() |
||||
const error = ref() |
||||
const form = reactive({ |
||||
email: '', |
||||
password: '' |
||||
}) |
||||
const userStore = user |
||||
|
||||
</script> |
||||
<script lang="ts"> |
||||
import {useNuxtApp} from "nuxt/app"; |
||||
import {Api} from "nocodb-sdk"; |
||||
|
||||
|
||||
// const {$api}: { $api: Api<any> } = useNuxtApp() as any |
||||
export default { |
||||
methods: { |
||||
signIn() { |
||||
this.$api.auth.signin(this.form).then((res) => { |
||||
console.log(res) |
||||
this.userStore.token = res |
||||
}) |
||||
const {user, setToken} = useUser() |
||||
|
||||
const signIn = async () => { |
||||
error.value = null |
||||
try { |
||||
const {token} = await $api.auth.signin(form) |
||||
setToken(token) |
||||
} catch (e) { |
||||
error.value = await extractSdkResponseErrorMsg(e) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
</script> |
||||
<style scoped> |
||||
|
||||
</style> |
||||
|
@ -1,13 +1,62 @@
|
||||
<template> |
||||
<div> |
||||
<!-- Enter your work email --> |
||||
<Card class="p-4 mx-auto mt-5" style="max-width: 500px"> |
||||
<template #content> |
||||
|
||||
<Message class="" v-if="error" severity="error">{{ error }}</Message> |
||||
|
||||
<div class="p-float-label mt-5"> |
||||
<InputText id="email" type="text" v-model="form.email" style="width:100%"/> |
||||
<label for="email">Email</label> |
||||
</div> |
||||
|
||||
<!-- Enter your password --> |
||||
<div class="p-float-label mt-5"> |
||||
<InputText id="password" type="password" v-model="form.password" style="width:100%"/> |
||||
<label for="password">Password</label> |
||||
</div> |
||||
|
||||
<div class="text-center"> |
||||
<Button |
||||
class="mt-5" |
||||
@click="signUp" |
||||
> |
||||
<b>Sign Up</b> |
||||
</Button> |
||||
</div> |
||||
</template> |
||||
</Card> |
||||
</div> |
||||
|
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "signup" |
||||
<script setup lang="ts"> |
||||
import {ref, reactive} from 'vue' |
||||
import {useUser} from "~/composables/user"; |
||||
import {Api} from "nocodb-sdk"; |
||||
import {extractSdkResponseErrorMsg} from "~/helpers/errorUtils"; |
||||
|
||||
const {$api}: { $api: Api<any> } = useNuxtApp() as any |
||||
const valid = ref() |
||||
const error = ref() |
||||
const form = reactive({ |
||||
email: '', |
||||
password: '' |
||||
}) |
||||
const {user, setToken} = useUser() |
||||
|
||||
const signUp = async () => { |
||||
error.value = null |
||||
try { |
||||
const {token} = await $api.auth.signup(form) |
||||
setToken(token) |
||||
} catch (e) { |
||||
error.value = await extractSdkResponseErrorMsg(e) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
</script> |
||||
<style scoped> |
||||
|
||||
</style> |
||||
|
Loading…
Reference in new issue