diff --git a/app/assets/styles/_base.scss b/app/assets/styles/_base.scss index 797f371..abdba62 100644 --- a/app/assets/styles/_base.scss +++ b/app/assets/styles/_base.scss @@ -87,4 +87,8 @@ // Small inputs --input-small-height: 2rem; --input-small-border-radius: 0.75rem; + + // Extra small inputs + --input-extra-small-height: 1.5rem; + --input-extra-small-border-radius: 0.5rem; } diff --git a/app/assets/styles/_utils.scss b/app/assets/styles/_utils.scss new file mode 100644 index 0000000..af42b6b --- /dev/null +++ b/app/assets/styles/_utils.scss @@ -0,0 +1,6 @@ +// overflow ellipsis +@mixin overflow-ellipsis { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/app/components/IconButton.vue b/app/components/IconButton.vue index a99195c..0746cfe 100644 --- a/app/components/IconButton.vue +++ b/app/components/IconButton.vue @@ -1,6 +1,6 @@ diff --git a/app/components/equipment/EquipmentTable.vue b/app/components/equipment/EquipmentTable.vue index 5ddc6b2..2faccbd 100644 --- a/app/components/equipment/EquipmentTable.vue +++ b/app/components/equipment/EquipmentTable.vue @@ -85,7 +85,7 @@ } - diff --git a/app/composables/use-checklist-items-data.ts b/app/composables/use-checklist-items-data.ts index 64ac276..7ae53d2 100644 --- a/app/composables/use-checklist-items-data.ts +++ b/app/composables/use-checklist-items-data.ts @@ -1,12 +1,8 @@ -import type { ChecklistItemModel } from "~/models/checklist" - export async function useChecklistItemsData(checklistId: string) { - const items = ref([]) + const { items } = useChecklistStore() const { data } = await useFetch(`/api/checklists/${checklistId}/items`) if (data.value !== undefined) { items.value = data.value } - - return items } diff --git a/app/composables/use-checklist-store.ts b/app/composables/use-checklist-store.ts new file mode 100644 index 0000000..3a32f24 --- /dev/null +++ b/app/composables/use-checklist-store.ts @@ -0,0 +1,44 @@ +import type { ChecklistItemModel } from "~/models/checklist"; + +export function useChecklistStore() { + const items = useState('checklistItems', () => []) + + async function removeItem(checklistId: string, itemId: string) { + try { + const deletedItem = await $fetch(`/api/checklists/${checklistId}/items/${itemId}`, { + method: 'DELETE' + }); + + items.value = items.value.filter(item => item.id !== deletedItem.id); + } catch (error) { + console.error(error); + } + } + + async function addItem(checklistId: string, equipmentId: string) { + try { + const insertedItem = await $fetch(`/api/checklists/${checklistId}/items`, { + method: 'POST', + + body: { + equipmentId + } + }); + + if (insertedItem !== undefined) { + items.value.push({ + id: insertedItem.id, + equipment: insertedItem.equipment + }) + } + } catch (error) { + console.error(error); + } + } + + return { + items, + removeItem, + addItem + } +} diff --git a/app/pages/checklists/[id].vue b/app/pages/checklists/[checklistId].vue similarity index 76% rename from app/pages/checklists/[id].vue rename to app/pages/checklists/[checklistId].vue index d879fa5..18d35bd 100644 --- a/app/pages/checklists/[id].vue +++ b/app/pages/checklists/[checklistId].vue @@ -18,7 +18,7 @@ @search="search" > @@ -42,13 +42,15 @@ } const route = useRoute() + const { items, addItem } = useChecklistStore() const name = ref('') - const checklistId = route.params.id?.toString() ?? '' + const checklistId = route.params.checklistId?.toString() ?? '' const isDeleting = ref(false) - const options = ref([]); - const isSearching = ref(false); + const options = ref([]) + const isSearching = ref(false) const { data: checklistData } = await useFetch(`/api/checklists/${checklistId}`) - const items = await useChecklistItemsData(checklistId) + + await useChecklistItemsData(checklistId) if (checklistData.value !== undefined) { name.value = checklistData.value.name @@ -100,27 +102,10 @@ } } - async function addItem(item: InventoryItem) { - try { - const insertedItem = await $fetch(`/api/checklists/${checklistId}/items`, { - method: 'POST', - - body: { - equipmentId: item.id - } - }) + async function handleOptionClick(equipmentId: string) { + await addItem(checklistId, equipmentId) - options.value = options.value.filter(({ id }) => id !== item.id) - - if (insertedItem !== undefined) { - items.value.push({ - id: insertedItem.id, - equipment: insertedItem.equipment - }) - } - } catch (error) { - console.error(error) - } + options.value = options.value.filter(({ id }) => id !== equipmentId) } diff --git a/nuxt.config.ts b/nuxt.config.ts index 27553d5..55405a7 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -37,7 +37,11 @@ export default defineNuxtConfig({ preprocessorOptions: { scss: { api: 'modern-compiler', - additionalData: '@use "~/assets/styles/media" as *;' + + additionalData: ` +@use "~/assets/styles/media" as *; +@use "~/assets/styles/utils" as *; +` } } } diff --git a/server/api/checklists/[id]/index.delete.ts b/server/api/checklists/[checklistId]/index.delete.ts similarity index 83% rename from server/api/checklists/[id]/index.delete.ts rename to server/api/checklists/[checklistId]/index.delete.ts index e801071..3475715 100644 --- a/server/api/checklists/[id]/index.delete.ts +++ b/server/api/checklists/[checklistId]/index.delete.ts @@ -3,8 +3,8 @@ import { validateId } from '~~/server/utils/validate' export default defineEventHandler(async (event) => { const userId = await validateSessionUser(event) - const id = getRouterParam(event, 'id') - const checklistId = validateId(id) + const checklistIdParam = getRouterParam(event, 'checklistId') + const checklistId = validateId(checklistIdParam) const deleted = await event.context.db .delete(tables.checklists) diff --git a/server/api/checklists/[id]/index.get.ts b/server/api/checklists/[checklistId]/index.get.ts similarity index 85% rename from server/api/checklists/[id]/index.get.ts rename to server/api/checklists/[checklistId]/index.get.ts index 62738c9..8fd4151 100644 --- a/server/api/checklists/[id]/index.get.ts +++ b/server/api/checklists/[checklistId]/index.get.ts @@ -8,8 +8,8 @@ interface ReturnData { export default defineEventHandler(async (event) : Promise => { const userId = await validateSessionUser(event) - const id = getRouterParam(event, 'id') - const checklistId = validateId(id) + const checklistIdParam = getRouterParam(event, 'checklistId') + const checklistId = validateId(checklistIdParam) const result = await event.context.db.query.checklists .findFirst({ diff --git a/server/api/checklists/[checklistId]/items/[itemId].delete.ts b/server/api/checklists/[checklistId]/items/[itemId].delete.ts new file mode 100644 index 0000000..80733f0 --- /dev/null +++ b/server/api/checklists/[checklistId]/items/[itemId].delete.ts @@ -0,0 +1,50 @@ +import { and, eq, exists } from 'drizzle-orm' +import * as v from 'valibot' + +const paramsSchema = v.object({ + checklistId: idValidator, + itemId: idValidator +}) + +type ParamsData = v.InferOutput + +function validateParams(params: unknown) { + return v.parse(paramsSchema, params) +} + +export default defineEventHandler(async (event) => { + const { db } = event.context + const userId = await validateSessionUser(event) + const { checklistId, itemId } = await getValidatedRouterParams(event, validateParams) + + const [deletedItem] = await db.delete(tables.checklistItems) + .where( + and( + eq(tables.checklistItems.id, itemId), + eq(tables.checklistItems.checklistId, checklistId), + exists( + db.select({ + id: tables.checklists.id + }) + .from(tables.checklists) + .where( + and( + eq(tables.checklists.id, checklistId), + eq(tables.checklists.userId, userId) + ) + ) + ) + ) + ) + .returning({ + id: tables.checklistItems.id + }) + + if (deletedItem === undefined) { + throw createError({ + statusCode: 404 + }) + } + + return deletedItem +}) diff --git a/server/api/checklists/[id]/items.get.ts b/server/api/checklists/[checklistId]/items/index.get.ts similarity index 93% rename from server/api/checklists/[id]/items.get.ts rename to server/api/checklists/[checklistId]/items/index.get.ts index 3226dd3..23ca862 100644 --- a/server/api/checklists/[id]/items.get.ts +++ b/server/api/checklists/[checklistId]/items/index.get.ts @@ -2,7 +2,7 @@ import { and, asc, eq } from 'drizzle-orm' export default defineEventHandler(async (event) => { const userId = await validateSessionUser(event) - const checklistIdParam = getRouterParam(event, 'id') + const checklistIdParam = getRouterParam(event, 'checklistId') const checklistId = validateId(checklistIdParam) const result = await event.context.db diff --git a/server/api/checklists/[id]/items.post.ts b/server/api/checklists/[checklistId]/items/index.post.ts similarity index 91% rename from server/api/checklists/[id]/items.post.ts rename to server/api/checklists/[checklistId]/items/index.post.ts index 4bcf5e5..00e30ab 100644 --- a/server/api/checklists/[id]/items.post.ts +++ b/server/api/checklists/[checklistId]/items/index.post.ts @@ -2,10 +2,7 @@ import { eq } from 'drizzle-orm' import * as v from 'valibot' const bodySchema = v.object({ - equipmentId: v.pipe( - v.string(), - v.nonEmpty() - ) + equipmentId: idValidator }) type BodyData = v.InferOutput @@ -19,7 +16,7 @@ export default defineEventHandler(async (event) => { await validateSessionUser(event) - const checklistIdParam = getRouterParam(event, 'id') + const checklistIdParam = getRouterParam(event, 'checklistId') const checklistId = validateId(checklistIdParam) const { equipmentId } = await readValidatedBody(event, validateBody) diff --git a/server/utils/validate.ts b/server/utils/validate.ts index 5c2679e..161e0cd 100644 --- a/server/utils/validate.ts +++ b/server/utils/validate.ts @@ -1,6 +1,8 @@ import * as v from 'valibot' import type { H3Event, EventHandlerRequest } from 'h3' +export const idValidator = v.pipe(v.string(), v.nonEmpty()) + export async function validateSessionUser(event: H3Event) { const session = await useAppSession(event) const { userId } = session.data @@ -15,8 +17,7 @@ export async function validateSessionUser(event: H3Event) { } export function validateId(id: unknown) { - const schema = v.pipe(v.string(), v.nonEmpty()) - const { issues, output, success } = v.safeParse(schema, id) + const { issues, output, success } = v.safeParse(idValidator, id) if (success) { return output