Skip to content

Commit 389ccbd

Browse files
authored
fix: UI/UX/DevX improvements (#36)
* feat: field type badges in the fields list table * fix: proper loading state for reset panel in the switchboard * fix: auto scroll to top in router * fix: tiny change in backend Settings * chore: restructured docker-compose.yml * fix: tiny change in frontend api.ts * fix: nice empty state in tags grid
1 parent f5a8fe4 commit 389ccbd

File tree

9 files changed

+69
-44
lines changed

9 files changed

+69
-44
lines changed

backend/app/settings.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44
from typing import Literal, Optional
55

6-
from pydantic import ConfigDict
6+
from pydantic import ConfigDict, Field
77
from pydantic_settings import BaseSettings
88

99

@@ -14,7 +14,7 @@ def resolve_env_file(base_dir: str = ".") -> str:
1414

1515

1616
class Settings(BaseSettings):
17-
env: Literal["dev", "prod", "demo"] = "dev"
17+
env: Literal["dev", "prod", "demo"] = Field(default="dev", alias="ENV")
1818
database_url: str = "sqlite:///./test.db"
1919
frontend_url: Optional[str] = None
2020

@@ -29,6 +29,7 @@ class Settings(BaseSettings):
2929
model_config = ConfigDict(
3030
env_file=resolve_env_file(),
3131
env_file_encoding="utf-8",
32+
case_sensitive=False,
3233
)
3334

3435
@property

docker-compose.yaml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,4 @@
11
services:
2-
db:
3-
image: postgres:15
4-
container_name: evsy-db
5-
restart: always
6-
environment:
7-
POSTGRES_USER: evsy
8-
POSTGRES_PASSWORD: evsy
9-
POSTGRES_DB: evsy
10-
volumes:
11-
- postgres_data:/var/lib/postgresql/data
12-
# ports:
13-
# - "5432:5432"
14-
healthcheck:
15-
test: ["CMD", "pg_isready", "-U", "evsy"]
16-
interval: 10s
17-
timeout: 5s
18-
retries: 5
19-
202
backend:
213
build:
224
context: ./backend
@@ -52,5 +34,23 @@ services:
5234
backend:
5335
condition: service_healthy
5436

37+
db:
38+
image: postgres:15
39+
container_name: evsy-db
40+
restart: always
41+
environment:
42+
POSTGRES_USER: evsy
43+
POSTGRES_PASSWORD: evsy
44+
POSTGRES_DB: evsy
45+
volumes:
46+
- postgres_data:/var/lib/postgresql/data
47+
# ports:
48+
# - "5432:5432"
49+
healthcheck:
50+
test: ["CMD", "pg_isready", "-U", "evsy"]
51+
interval: 10s
52+
timeout: 5s
53+
retries: 5
54+
5555
volumes:
5656
postgres_data:

frontend/src/modules/fields/components/FieldTypeBadge.vue

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
import { FieldType } from '@/modules/fields/types'
33
import { Badge } from '@/shared/ui/badge'
44
import { cn } from '@/shared/utils/general' // your utility for class merging
5-
import { useAttrs } from 'vue'
5+
import { computed, useAttrs } from 'vue'
66
7-
const props = defineProps<{
7+
interface Props {
88
type: FieldType
99
monochrome?: boolean
10-
}>()
10+
}
11+
12+
const props = withDefaults(defineProps<Props>(), {
13+
monochrome: false,
14+
})
1115
1216
const attrs = useAttrs()
1317
@@ -31,10 +35,12 @@ const colorClass = {
3135
3236
const baseClass = 'text-xs tracking-widest uppercase'
3337
34-
const finalClass = cn(
35-
baseClass,
36-
props.monochrome ? 'bg-muted text-foreground' : colorClass[props.type],
37-
attrs.class as string | undefined
38+
const color = computed(() =>
39+
props.monochrome ? 'bg-muted text-foreground' : colorClass[props.type]
40+
)
41+
42+
const finalClass = computed(() =>
43+
cn(baseClass, color, color.value, attrs.class as string | undefined)
3844
)
3945
</script>
4046

frontend/src/modules/fields/components/fieldColumns.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { h } from 'vue'
2-
import type { Field } from '@/modules/fields/types.ts'
2+
import type { Field, FieldType } from '@/modules/fields/types.ts'
33
import type { ColumnDef } from '@tanstack/vue-table'
44
import FieldsDataTableDropdown from '@/modules/fields/components/FieldsDataTableDropdown.vue'
55
import { RouterLink } from 'vue-router'
66
import DataTableColumnHeader from '@/shared/components/data/DataTableColumnHeader.vue'
77
import { fieldsTableFilter } from '@/shared/utils/tableFilters'
8+
import FieldTypeBadge from '@/modules/fields/components/FieldTypeBadge.vue'
89

910
export function getFieldColumns(
1011
onEdit: (field: Field) => void,
@@ -63,7 +64,7 @@ export function getFieldColumns(
6364
accessorKey: 'field_type',
6465
enableHiding: false,
6566
meta: {
66-
class: 'w-[10ch]',
67+
class: 'max-w-[10ch] text-left',
6768
headerClass: 'w-[10ch]',
6869
},
6970
header: ({ column }) =>
@@ -73,7 +74,7 @@ export function getFieldColumns(
7374
}),
7475
cell: ({ row }) => {
7576
const field_type = String(row.getValue('field_type'))
76-
return h('div', { class: 'text-left font-medium' }, field_type)
77+
return h(FieldTypeBadge, { type: field_type as FieldType }, field_type)
7778
},
7879
filterFn: (row, id, value) => {
7980
return value.includes(row.getValue(id))
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { api } from '@/shared/utils/api'
22
import type { ExportBundle, ImportBundle, ResetPreview } from './types'
33

4-
export const seedDatabase = () => api.post('/admin/seed')
4+
export const seedDatabase = () => api.post('/admin/seed/')
55

66
export const resetDatabase = (dryRun = false) =>
77
api
8-
.post<ResetPreview | { status: string }>('/admin/reset', null, {
8+
.post<ResetPreview | { status: string }>('/admin/reset/', null, {
99
params: { dry_run: dryRun },
1010
})
1111
.then(r => r.data)
1212

1313
export const importData = (payload: ImportBundle, source = 'json') =>
14-
api.post('/admin/io/import', payload, { params: { source } })
14+
api.post('/admin/io/import/', payload, { params: { source } })
1515

1616
export const exportData = (target = 'json') =>
17-
api.get<ExportBundle>('/admin/io/export', { params: { target } })
17+
api.get<ExportBundle>('/admin/io/export/', { params: { target } })

frontend/src/modules/switchboard/components/ResetPanel.vue

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import { Button } from '@/shared/ui/button'
66
import { useEnhancedToast } from '@/shared/composables/useEnhancedToast'
77
import type { ResetPreview } from '../types'
88
import { Badge } from '@/shared/ui/badge'
9+
import { Skeleton } from '@/shared/ui/skeleton'
910
1011
const queryClient = useQueryClient()
1112
const { showSuccess } = useEnhancedToast()
1213
13-
const { data: preview } = useQuery({
14+
const { data: preview, isLoading } = useQuery({
1415
queryKey: ['resetPreview'],
1516
queryFn: () => resetDatabase(true),
1617
select: data => (data as ResetPreview).would_delete,
@@ -35,24 +36,35 @@ const { mutate: handleReset, isPending: isResetting } = useMutation({
3536
<div class="flex flex-wrap items-center gap-4">
3637
<Button variant="destructive" :disabled="isResetting" @click="handleReset"> Reset </Button>
3738
<div
38-
class="text-muted-foreground flex flex-wrap items-center gap-2 text-xs transition-opacity duration-300"
39+
v-if="!isLoading"
40+
class="text-muted-foreground flex flex-wrap items-center gap-2 font-mono text-xs transition-opacity duration-300"
3941
>
40-
<Badge variant="outline" class="min-h-[1.5rem] min-w-[14ch] text-center font-mono">
42+
<Badge variant="outline" class="min-h-[1.5rem] min-w-[14ch] text-center">
4143
<Transition name="fade-slide" mode="out-in">
4244
<span v-if="preview" :key="preview.events">{{ preview.events }} events</span>
4345
</Transition>
4446
</Badge>
45-
<Badge variant="outline" class="min-h-[1.5rem] min-w-[14ch] text-center font-mono">
47+
48+
<Badge variant="outline" class="min-h-[1.5rem] min-w-[14ch] text-center">
4649
<Transition name="fade-slide" mode="out-in">
4750
<span v-if="preview" :key="preview.fields">{{ preview.fields }} fields</span>
4851
</Transition>
4952
</Badge>
50-
<Badge variant="outline" class="min-h-[1.5rem] min-w-[14ch] text-center font-mono">
53+
54+
<Badge variant="outline" class="min-h-[1.5rem] min-w-[14ch] text-center">
5155
<Transition name="fade-slide" mode="out-in">
5256
<span v-if="preview" :key="preview.tags">{{ preview.tags }} tags</span>
5357
</Transition>
5458
</Badge>
5559
</div>
60+
<div
61+
v-if="isLoading"
62+
class="text-muted-foreground flex flex-wrap items-center gap-2 font-mono text-xs transition-opacity duration-300"
63+
>
64+
<Skeleton class="h-[1.5rem] w-[14ch] rounded-md" />
65+
<Skeleton class="h-[1.5rem] w-[14ch] rounded-md" />
66+
<Skeleton class="h-[1.5rem] w-[14ch] rounded-md" />
67+
</div>
5668
</div>
5769
</template>
5870
</SwitchboardSection>

frontend/src/modules/tags/components/TagsDataGrid.vue

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,8 @@ const handleSearchKeydown = (event: KeyboardEvent) => {
6262
</template>
6363

6464
<template v-else>
65-
<div class="col-span-full flex items-center justify-center py-6">
66-
<div class="text-center">
67-
<div class="text-sm">No results.</div>
68-
</div>
65+
<div class="col-span-full w-full rounded-lg border p-4 text-center text-sm">
66+
<div>No results.</div>
6967
</div>
7068
</template>
7169
</div>

frontend/src/router/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ const publicPages = ['/login', '/signup', '/landing', '/oauth/callback']
77
const router = createRouter({
88
history: createWebHistory(),
99
routes: routes,
10+
scrollBehavior(to, from, savedPosition) {
11+
if (savedPosition) {
12+
return savedPosition
13+
} else {
14+
return { top: 0, behavior: 'smooth' }
15+
}
16+
},
1017
})
1118

1219
router.beforeEach((to, _from, next) => {

frontend/src/shared/utils/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useAuthStore } from '@/modules/auth/stores/useAuthStore'
33
import router from '@/router'
44

55
export const api = axios.create({
6-
baseURL: import.meta.env.VITE_API_URL ?? '/api/v1',
6+
baseURL: import.meta.env.VITE_API_URL || '/api/v1',
77
headers: {
88
'Content-Type': 'application/json',
99
},

0 commit comments

Comments
 (0)