Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/app/api/v1/routes/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ def get_event(event_id: int, db: Session = Depends(get_db)):
description="Return a paginated list of all events with their tags and fields.",
responses={200: {"description": "List of events returned"}},
)
def get_events(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
events = crud.get_events(db=db, skip=skip, limit=limit)
def get_events(db: Session = Depends(get_db)):
events = crud.get_events(db=db)
return events


Expand Down
4 changes: 2 additions & 2 deletions backend/app/api/v1/routes/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ def create_field(field: schemas.FieldCreate, db: Session = Depends(get_db)):
200: {"description": "List of fields returned"},
},
)
def get_fields(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
fields = crud.get_fields(db=db, skip=skip, limit=limit)
def get_fields(db: Session = Depends(get_db)):
fields = crud.get_fields(db=db)
return fields


Expand Down
4 changes: 2 additions & 2 deletions backend/app/api/v1/routes/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ def create_tag(tag: schemas.TagCreate, db: Session = Depends(get_db)):
200: {"description": "List of tags returned"},
},
)
def get_tags(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
tags = crud.get_tags(db=db, skip=skip, limit=limit)
def get_tags(db: Session = Depends(get_db)):
tags = crud.get_tags(db=db)
return tags


Expand Down
15 changes: 8 additions & 7 deletions backend/app/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ def get_event(db: Session, event_id: int):
return db.query(models.Event).filter(models.Event.id == event_id).first()


def get_events(db: Session, skip: int = 0, limit: int = 100):
def get_events(db: Session):
return (
db.query(models.Event)
.options(
joinedload(models.Event.tags),
joinedload(models.Event.fields),
)
.offset(skip)
.limit(limit)
.order_by(models.Event.id)
.all()
)

Expand Down Expand Up @@ -87,8 +86,10 @@ def create_tag(db: Session, tag: schemas.TagCreate):
return db_tag


def get_tags(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Tag).offset(skip).limit(limit).all()
def get_tags(db: Session):
return (
db.query(models.Tag).order_by(models.Tag.created_at.desc(), models.Tag.id).all()
)


def get_tag(db: Session, tag_id: str):
Expand Down Expand Up @@ -143,8 +144,8 @@ def create_field(db: Session, field: schemas.FieldCreate):
return db_field


def get_fields(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Field).offset(skip).limit(limit).all()
def get_fields(db: Session):
return db.query(models.Field).order_by(models.Field.id).all()


def get_field(db: Session, field_id: int):
Expand Down
2 changes: 2 additions & 0 deletions backend/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Event(Base, TimestampMixin):
secondary="event_tags",
back_populates="events",
viewonly=True,
order_by="Tag.id.asc()",
)

field_links = relationship(
Expand All @@ -54,6 +55,7 @@ class Event(Base, TimestampMixin):
secondary="event_fields",
back_populates="events",
viewonly=True,
order_by="Field.id.asc()",
)


Expand Down
4 changes: 2 additions & 2 deletions frontend/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"aliases": {
"components": "@/components",
"composables": "@/composables",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"utils": "@/shared/utils/general",
"ui": "@/shared/ui",
"lib": "@/lib"
},
"iconLibrary": "lucide"
Expand Down
12 changes: 6 additions & 6 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"clsx": "^2.1.1",
"lucide-vue-next": "^0.488.0",
"pinia": "^3.0.2",
"reka-ui": "^2.2.0",
"reka-ui": "^2.2.1",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.4",
"tw-animate-css": "^1.2.5",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Toaster } from '@/shared/ui/sonner'

<template>
<Toaster richColors />

<MainLayout>
<RouterView v-slot="{ Component }">
<Transition name="page" mode="out-in" appear>
Expand Down
1 change: 0 additions & 1 deletion frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--transition-bg-color: background-color, color;
}

/* Light theme */
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/modules/events/components/EventForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ function removeTag(tagId: string) {
<FormControl>
<TagsInput
v-model="componentField.modelValue"
class="flex h-9 w-full justify-between gap-1 shadow-xs"
class="border-input focus-within:border-ring focus-within:ring-ring/50 flex h-9 w-full justify-between gap-1 rounded-md border bg-transparent shadow-xs transition-[box-shadow,color,border] focus-within:ring-[3px]"
>
<div class="h-5 w-[30%] p-0">
<ComboboxInput v-model="searchTerm" as-child>
Expand All @@ -179,7 +179,7 @@ function removeTag(tagId: string) {

<TagScrollArea>
<TagsInputItem
v-for="item in componentField.modelValue"
v-for="item in [...componentField.modelValue].reverse()"
:key="item"
:value="item"
>
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/modules/events/components/EventsDataTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import DataTable from '@/shared/components/data/DataTable.vue'
import { useDataTable } from '@/shared/composables/useDataTable'
import DataTableLayout from '@/shared/components/data/DataTableLayout.vue'
import DataTableSkeleton from '@/shared/components/skeletons/DataTableSkeleton.vue'
import DataTableSingleSelectFilter from '@/shared/components/data/DataTableSingleSelectFilter.vue'
import type { Tag } from '@/modules/tags/types'

const props = defineProps<{
columns: ColumnDef<TData, TValue>[]
data: TData[]
tags: Tag[]
isLoading: boolean
isLoadingTags: boolean
}>()

const { table } = useDataTable({
Expand All @@ -31,6 +35,14 @@ const { table } = useDataTable({
:column="table.getColumn('name')!"
placeholder="Filter by name..."
/>

<!-- Tag Filter -->
<DataTableSingleSelectFilter
:column="table.getColumn('tags')"
placeholder="Select a tag..."
:options="tags.map(tag => tag.id)"
:disabled="isLoadingTags"
/>
</template>
<template #buttons>
<Button as-child>
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/modules/events/components/eventColumns.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { h } from 'vue'
import type { Event } from '@/modules/events/types.ts'
import type { Tag } from '@/modules/tags/types.ts'
import type { ColumnDef } from '@tanstack/vue-table'
import EventsDataTableDropdown from '@/modules/events/components/EventsDataTableDropdown.vue'
import { RouterLink } from 'vue-router'
Expand Down Expand Up @@ -113,6 +114,10 @@ export function getEventColumns(
() => tags.map(tag => h(Badge, { variant: 'outline' }, { default: () => tag.id }))
)
},
filterFn: (row, id, value) => {
const tags = row.getValue(id) as Tag[]
return tags.some(tag => tag.id === value)
},
},
{
id: 'actions',
Expand Down
17 changes: 15 additions & 2 deletions frontend/src/modules/events/pages/EventsPage.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { eventApi } from '@/modules/events/api'
import { tagApi } from '@/modules/tags/api'
import type { Event } from '@/modules/events/types'
import type { Tag } from '@/modules/tags/types'
import EventsDataTable from '../components/EventsDataTable.vue'
import Header from '@/shared/components/layout/PageHeader.vue'
import { useAsyncTask } from '@/shared/composables/useAsyncTask'
Expand All @@ -14,9 +16,11 @@ import DeleteModal from '@/shared/components/modals/DeleteModal.vue'
const { run, isLoading } = useAsyncTask()
const { run: runDeleteTask, isLoading: isDeleting } = useAsyncTask()
const { run: runUpdateTask, isLoading: isSaving } = useAsyncTask()
const { run: loadTags, isLoading: isLoadingTags } = useAsyncTask()
const { showUpdated, showDeleted } = useEnhancedToast()

const events = ref<Event[]>([])
const tags = ref<Tag[]>([])

const selectedEventId = ref<number | null>(null)
const editedEvent = ref<Event | null>(null)
Expand Down Expand Up @@ -76,14 +80,23 @@ onMounted(() => {
events.value = data
}
)
loadTags(async () => {
tags.value = await tagApi.getAll()
})
})
</script>

<template>
<div>
<Header title="Events" />
<div class="container mx-auto overflow-x-auto">
<EventsDataTable :columns="columns" :data="events" :isLoading="isLoading" />
<div class="container mx-auto">
<EventsDataTable
:columns="columns"
:data="events"
:tags="tags"
:isLoading="isLoading"
:isLoadingTags="isLoadingTags"
/>
</div>

<!-- Modals -->
Expand Down
15 changes: 5 additions & 10 deletions frontend/src/modules/fields/components/FieldsDataTable.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
<script setup lang="ts" generic="TData, TValue">
import type { ColumnDef } from '@tanstack/vue-table'
import DataTableInputFilter from '@/shared/components/data/DataTableInputFilter.vue'
import DataTableSingleSelectFilter from '@/shared/components/data/DataTableSingleSelectFilter.vue'
import { Icon } from '@iconify/vue'
import { computed } from 'vue'
import { Button } from '@/shared/ui/button'
import DataTablePagination from '@/shared/components/data/DataTablePagination.vue'
import { FieldType } from '@/modules/fields/types'
import DataTable from '@/shared/components/data/DataTable.vue'
import { useDataTable } from '@/shared/composables/useDataTable'
import DataTableLayout from '@/shared/components/data/DataTableLayout.vue'
import DataTableSkeleton from '@/shared/components/skeletons/DataTableSkeleton.vue'
import DataTableMultiSelectFilter from '@/shared/components/data/DataTableMultiSelectFilter.vue'

const props = defineProps<{
columns: ColumnDef<TData, TValue>[]
data: TData[]
isLoading: boolean
}>()

const { table, columnFilters } = useDataTable({
const { table } = useDataTable({
data: () => props.data,
columns: () => props.columns,
defaultSorting: [{ id: 'id', desc: true }],
})

const fieldTypes = Object.values(FieldType)
const isTypeFilterSet = computed(() =>
columnFilters.value.some(f => f.id === 'field_type' && !!f.value)
)
</script>

<template>
Expand All @@ -41,11 +37,10 @@ const isTypeFilterSet = computed(() =>
/>

<!-- Type Filter -->
<DataTableSingleSelectFilter
:column="table.getColumn('field_type')!"
placeholder="Select a type..."
<DataTableMultiSelectFilter
:column="table.getColumn('field_type')"
title="Type"
:options="fieldTypes"
:show-clear-button="isTypeFilterSet"
/>
</template>
<template #buttons>
Expand Down
16 changes: 10 additions & 6 deletions frontend/src/modules/fields/components/LinkedFieldsSelector.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { Field } from '@/modules/fields/types'
import Skeleton from '@/shared/ui/skeleton/Skeleton.vue'
import Checkbox from '@/shared/ui/checkbox/Checkbox.vue'

defineProps<{
fields: Field[]
Expand All @@ -22,14 +23,17 @@ const emit = defineEmits<{
<Transition name="fade" appear>
<div v-if="!isLoading">
<div v-for="field in fields" :key="field.id" class="flex items-center gap-2">
<input
type="checkbox"
:checked="selectedIds.includes(field.id)"
@change="() => emit('toggle', field.id)"
<Checkbox
:id="`field-${field.id}`"
class="form-checkbox"
:model-value="selectedIds.includes(field.id)"
@update:model-value="() => emit('toggle', field.id)"
/>
<label :for="`field-${field.id}`" class="text-sm">{{ field.name }}</label>
<label
:for="`field-${field.id}`"
class="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{{ field.name }}
</label>
</div>
</div>
</Transition>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/modules/fields/components/fieldColumns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ export function getFieldColumns(
const field_type = String(row.getValue('field_type'))
return h('div', { class: 'text-left font-medium' }, field_type)
},
filterFn: 'equals',
filterFn: (row, id, value) => {
return value.includes(row.getValue(id))
},
},
{
accessorKey: 'description',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/modules/fields/pages/FieldsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ onMounted(() => {
<template>
<div>
<Header title="Fields" />
<div class="container mx-auto overflow-x-auto">
<div class="container mx-auto">
<FieldsDataTable :columns="columns" :data="fields" :isLoading="isLoading" />
</div>

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/modules/tags/components/TagItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const handleCopyId = async () => {
<template>
<Transition name="fade" appear>
<div
class="flex items-start justify-between gap-4 rounded-lg border p-4 transition-shadow hover:shadow-sm"
class="flex items-start justify-between gap-4 rounded-lg border p-4 transition hover:shadow-sm"
>
<div>
<TooltipProvider :delay-duration="800">
Expand Down
Loading