Merge branch 'main' into i18n

This commit is contained in:
DoraTiger 2024-03-01 09:06:27 +00:00
commit 2881d3d447
51 changed files with 3169 additions and 2134 deletions

View file

@ -23,7 +23,7 @@ require (
github.com/yeqown/go-qrcode/v2 v2.2.2
github.com/yeqown/go-qrcode/writer/standard v1.2.2
golang.org/x/crypto v0.19.0
modernc.org/sqlite v1.29.1
modernc.org/sqlite v1.29.2
)
require (

View file

@ -111,8 +111,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
@ -122,8 +120,6 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olahol/melody v1.1.4 h1:RQHfKZkQmDxI0+SLZRNBCn4LiXdqxLKRGSkT8Dyoe/E=
github.com/olahol/melody v1.1.4/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
@ -141,10 +137,6 @@ github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -235,8 +227,8 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.29.1 h1:19GY2qvWB4VPw0HppFlZCPAbmxFU41r+qjKZQdQ1ryA=
modernc.org/sqlite v1.29.1/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
modernc.org/sqlite v1.29.2 h1:xgBSyA3gemwgP31PWFfFjtBorQNYpeypGdoSDjXhrgI=
modernc.org/sqlite v1.29.2/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View file

@ -799,8 +799,11 @@ func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID)
item.HasGroupWith(group.ID(GID)),
item.Or(
item.PurchaseTimeNotNil(),
item.PurchaseFromLT("0002-01-01"),
item.SoldTimeNotNil(),
item.SoldToLT("0002-01-01"),
item.WarrantyExpiresNotNil(),
item.WarrantyDetailsLT("0002-01-01"),
),
)
@ -819,16 +822,37 @@ func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID)
updateQ := e.db.Item.Update().Where(item.ID(i.ID))
if !i.PurchaseTime.IsZero() {
switch {
case i.PurchaseTime.Year() < 100:
updateQ.ClearPurchaseTime()
default:
updateQ.SetPurchaseTime(toDateOnly(i.PurchaseTime))
}
} else {
updateQ.ClearPurchaseTime()
}
if !i.SoldTime.IsZero() {
switch {
case i.SoldTime.Year() < 100:
updateQ.ClearSoldTime()
default:
updateQ.SetSoldTime(toDateOnly(i.SoldTime))
}
} else {
updateQ.ClearSoldTime()
}
if !i.WarrantyExpires.IsZero() {
switch {
case i.WarrantyExpires.Year() < 100:
updateQ.ClearWarrantyExpires()
default:
updateQ.SetWarrantyExpires(toDateOnly(i.WarrantyExpires))
}
} else {
updateQ.ClearWarrantyExpires()
}
_, err = updateQ.Save(ctx)
if err != nil {
@ -879,7 +903,6 @@ func (e *ItemsRepository) SetPrimaryPhotos(ctx context.Context, GID uuid.UUID) (
_, err = e.db.Attachment.UpdateOne(a).
SetPrimary(true).
Save(ctx)
if err != nil {
return updated, err
}

View file

@ -1 +1 @@
mkdocs-material==9.5.9
mkdocs-material==9.5.12

View file

@ -1,4 +1,6 @@
<script lang="ts" setup>
import MdiPlus from "~icons/mdi/mdi-plus";
const ctx = useAuthContext();
const api = useUserApi();
@ -103,7 +105,7 @@
<div class="dropdown">
<label tabindex="0" class="btn btn-primary btn-sm">
<span>
<Icon name="mdi-plus" class="mr-1 -ml-1" />
<MdiPlus class="mr-1 -ml-1" />
</span>
Create
</label>

View file

@ -32,7 +32,7 @@
<input ref="importRef" type="file" class="hidden" accept=".csv,.tsv" @change="setFile" />
<BaseButton type="button" @click="uploadCsv">
<Icon class="h-5 w-5 mr-2" name="mdi-upload" />
<MdiUpload class="h-5 w-5 mr-2" />
Upload
</BaseButton>
<p class="text-center pt-4 -mb-5">
@ -48,6 +48,7 @@
</template>
<script setup lang="ts">
import MdiUpload from "~icons/mdi/upload";
type Props = {
modelValue: boolean;
};

View file

@ -14,14 +14,14 @@
>
<div class="flex gap-1">
<template v-if="notify.type == 'success'">
<Icon name="heroicons-check" class="h-5 w-5" />
<MdiCheckboxMarkedCircle class="h-5 w-5" />
</template>
<template v-if="notify.type == 'info'">
<Icon name="heroicons-information-circle" class="h-5 w-5" />
<MdiInformationSlabCircle class="h-5 w-5" />
</template>
<template v-if="notify.type == 'error'">
<Icon name="heroicons-bell-alert" class="h-5 w-5" />
<MdiAlert class="h-5 w-5" />
</template>
{{ notify.message }}
</div>
@ -31,6 +31,10 @@
</template>
<script setup lang="ts">
import MdiCheckboxMarkedCircle from "~icons/mdi/checkbox-marked-circle";
import MdiInformationSlabCircle from "~icons/mdi/information-slab-circle";
import MdiAlert from "~icons/mdi/alert";
import { useNotifications } from "@/composables/use-notifier";
const { notifications, dropNotification } = useNotifications();

View file

@ -1,14 +0,0 @@
<template>
<div class="divider">
<div class="btn-group min-w-[180px] flex-nowrap">
<button name="options" class="btn btn-sm btn-primary" @click="$emit('edit')">
<Icon name="heroicons-pencil" class="h-5 w-5 mr-1" aria-hidden="true" />
<span> Edit </span>
</button>
<button name="options" class="btn btn-sm btn-primary" @click="$emit('delete')">
<Icon name="heroicons-trash" class="h-5 w-5 mr-1" aria-hidden="true" />
<span> Delete </span>
</button>
</div>
</div>
</template>

View file

@ -6,8 +6,8 @@
<slot name="title"></slot>
<template v-if="collapsable">
<span class="ml-2 swap swap-rotate" :class="`${collapsed ? 'swap-active' : ''}`">
<Icon class="h-6 w-6 swap-on" name="mdi-chevron-right" />
<Icon class="h-6 w-6 swap-off" name="mdi-chevron-down" />
<MdiChevronRight class="h-6 w-6 swap-on" />
<MdiChevronDown class="h-6 w-6 swap-off" />
</span>
</template>
</h3>
@ -34,6 +34,9 @@
</template>
<script setup lang="ts">
import MdiChevronDown from "~icons/mdi/chevron-down";
import MdiChevronRight from "~icons/mdi/chevron-right";
defineProps<{
collapsable?: boolean;
}>();

View file

@ -1,7 +1,7 @@
<template>
<div class="pb-3">
<h3
class="text-3xl font-bold tracking-tight"
class="text-3xl font-bold tracking-tight flex items-center"
:class="{
'text-neutral-content': dark,
'text-content': !dark,

View file

@ -16,10 +16,10 @@
class="absolute inset-y-0 right-6 flex items-center rounded-r-md px-2 focus:outline-none"
@click="clear"
>
<Icon name="mdi-close" class="w-5 h-5" />
<MdiClose class="w-5 h-5" />
</button>
<ComboboxButton class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
<Icon name="mdi-chevron-down" class="w-5 h-5" />
<MdiChevronDown class="w-5 h-5" />
</ComboboxButton>
<ComboboxOptions
v-if="computedItems.length > 0"
@ -49,7 +49,7 @@
active ? 'text-primary-content' : 'bg-primary',
]"
>
<Icon name="mdi-check" class="h-5 w-5" aria-hidden="true" />
<MdiCheck class="h-5 w-5" aria-hidden="true" />
</span>
</slot>
</li>
@ -61,6 +61,7 @@
</template>
<script setup lang="ts">
import lunr from "lunr";
import {
Combobox,
ComboboxInput,
@ -69,6 +70,9 @@
ComboboxButton,
ComboboxLabel,
} from "@headlessui/vue";
import MdiClose from "~icons/mdi/close";
import MdiChevronDown from "~icons/mdi/chevron-down";
import MdiCheck from "~icons/mdi/check";
type SupportValues = string | { [key: string]: any };
@ -126,42 +130,37 @@
return "";
}
const computedItems = computed<ComboItem[]>(() => {
const list: ComboItem[] = [];
function lunrFactory() {
return lunr(function () {
this.ref("id");
this.field("display");
for (let i = 0; i < props.items.length; i++) {
const item = props.items[i];
const out: Partial<ComboItem> = {
id: i,
value: item,
};
switch (typeof item) {
case "string":
out.display = item;
break;
case "object":
// @ts-ignore - up to the user to provide a valid display key
out.display = item[props.display] as string;
break;
default:
out.display = "";
break;
const display = extractDisplay(item);
this.add({ id: i, display });
}
});
}
if (search.value && out.display) {
const foldSearch = search.value.toLowerCase();
const foldDisplay = out.display.toLowerCase();
const index = ref<ReturnType<typeof lunrFactory>>(lunrFactory());
if (foldDisplay.startsWith(foldSearch)) {
list.push(out as ComboItem);
watchEffect(() => {
if (props.items) {
index.value = lunrFactory();
}
});
continue;
}
const computedItems = computed<ComboItem[]>(() => {
const list: ComboItem[] = [];
list.push(out as ComboItem);
const matches = index.value.search("*" + search.value + "*");
for (let i = 0; i < matches.length; i++) {
const match = matches[i];
const item = props.items[parseInt(match.ref)];
const display = extractDisplay(item);
list.push({ id: i, display, value: item });
}
return list;

View file

@ -9,11 +9,15 @@
<label class="label">
<span class="label-text"> {{ label }} </span>
</label>
<input v-model="selected" type="date" class="input input-bordered col-span-3 w-full mt-2" />
<VueDatePicker v-model="selected" :enable-time-picker="false" clearable :dark="isDark" />
</div>
</template>
<script setup lang="ts">
// @ts-ignore
import VueDatePicker from "@vuepic/vue-datepicker";
import "@vuepic/vue-datepicker/dist/main.css";
import * as datelib from "~/lib/datelib/datelib";
const emit = defineEmits(["update:modelValue", "update:text"]);
const props = defineProps({
@ -32,10 +36,10 @@
},
});
const selected = computed({
get() {
// return modelValue as string as YYYY-MM-DD or null
const isDark = useIsDark();
const selected = computed<Date | null>({
get() {
// String
if (typeof props.modelValue === "string") {
// Empty string
@ -48,26 +52,34 @@
return null;
}
// Valid Date string
return props.modelValue;
return datelib.parse(props.modelValue);
}
// Date
if (props.modelValue instanceof Date) {
if (props.modelValue.getFullYear() < 1000) {
return null;
}
if (isNaN(props.modelValue.getTime())) {
return null;
}
// Valid Date
return props.modelValue.toISOString().split("T")[0];
return props.modelValue;
}
return null;
},
set(value: string | null) {
// emit update:modelValue with a Date object or null
console.log("SET", value);
emit("update:modelValue", value ? new Date(value) : null);
set(value: Date | null) {
console.debug("DatePicker: SET", value);
if (value instanceof Date) {
value = datelib.zeroTime(value);
emit("update:modelValue", value);
} else {
value = value ? datelib.zeroTime(new Date(value)) : null;
emit("update:modelValue", value);
}
},
});
</script>

View file

@ -7,12 +7,14 @@
data-tip="Toggle Password Show"
@click="toggle()"
>
<Icon name="mdi-eye" class="h-5 w-5" />
<MdiEye name="mdi-eye" class="h-5 w-5" />
</button>
</div>
</template>
<script setup lang="ts">
import MdiEye from "~icons/mdi/eye";
type Props = {
modelValue: string;
placeholder?: string;

View file

@ -1,31 +0,0 @@
<script setup lang="ts">
import type { Ref } from "vue";
import type { IconifyIcon } from "@iconify/vue";
import { Icon as Iconify, loadIcon } from "@iconify/vue";
const nuxtApp = useNuxtApp();
const props = defineProps({
name: {
type: String,
required: true,
},
});
const icon: Ref<IconifyIcon | null> = ref(null);
const component = computed(() => nuxtApp.vueApp.component(props.name));
icon.value = await loadIcon(props.name).catch(() => null);
watch(
() => props.name,
async () => {
icon.value = await loadIcon(props.name).catch(() => null);
}
);
</script>
<template>
<Iconify v-if="icon" :icon="icon" class="inline-block" />
<Component :is="component" v-else-if="component" />
<span v-else>{{ name }}</span>
</template>

View file

@ -6,15 +6,15 @@
class="flex items-center justify-between py-3 pl-3 pr-4 text-sm"
>
<div class="flex w-0 flex-1 items-center">
<Icon name="mdi-paperclip" class="h-5 w-5 flex-shrink-0 text-gray-400" aria-hidden="true" />
<MdiPaperclip class="h-5 w-5 flex-shrink-0 text-gray-400" aria-hidden="true" />
<span class="ml-2 w-0 flex-1 truncate"> {{ attachment.document.title }}</span>
</div>
<div class="ml-4 flex-shrink-0">
<a class="tooltip mr-2" data-tip="Download" :href="attachmentURL(attachment.id)" target="_blank">
<Icon class="h-5 w-5" name="mdi-download" />
<MdiDownload class="h-5 w-5" />
</a>
<a class="tooltip" data-tip="Open" :href="attachmentURL(attachment.id)" target="_blank">
<Icon class="h-5 w-5" name="mdi-open-in-new" />
<MdiOpenInNew class="h-5 w-5" />
</a>
</div>
</li>
@ -23,6 +23,9 @@
<script setup lang="ts">
import { ItemAttachment } from "~~/lib/api/types/data-contracts";
import MdiPaperclip from "~icons/mdi/paperclip";
import MdiDownload from "~icons/mdi/download";
import MdiOpenInNew from "~icons/mdi/open-in-new";
const props = defineProps({
attachments: {

View file

@ -17,7 +17,7 @@
<div class="divider my-0"></div>
<div class="flex justify-between gap-2">
<div v-if="item.insured" class="tooltip z-10" data-tip="Insured">
<Icon class="h-5 w-5 text-primary" name="mdi-shield-check" />
<MdiShieldCheck class="h-5 w-5 text-primary" />
</div>
<div class="tooltip" data-tip="Quantity">
<span class="badge h-5 w-5 badge-primary badge-sm text-xs">
@ -35,6 +35,7 @@
<script setup lang="ts">
import { ItemOut, ItemSummary } from "~~/lib/api/types/data-contracts";
import MdiShieldCheck from "~icons/mdi/shield-check";
const api = useUserApi();

View file

@ -10,14 +10,14 @@
<div class="flex justify-center">
<BaseButton class="rounded-r-none" :loading="loading" type="submit">
<template #icon>
<Icon name="mdi-package-variant" class="swap-off h-5 w-5" />
<Icon name="mdi-package-variant-closed" class="swap-on h-5 w-5" />
<MdiPackageVariant class="swap-off h-5 w-5" />
<MdiPackageVariantClosed class="swap-on h-5 w-5" />
</template>
{{ $t("item.create.button1") }}
</BaseButton>
<div class="dropdown dropdown-top">
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
<Icon class="h-5 w-5" name="mdi-chevron-down" />
<MdiChevronDown class="h-5 w-5" name="mdi-chevron-down" />
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-64 right-0">
<li>
@ -38,6 +38,9 @@
import { ItemCreate, LabelOut, LocationOut } from "~~/lib/api/types/data-contracts";
import { useLabelStore } from "~~/stores/labels";
import { useLocationStore } from "~~/stores/locations";
import MdiPackageVariant from "~icons/mdi/package-variant";
import MdiPackageVariantClosed from "~icons/mdi/package-variant-closed";
import MdiChevronDown from "~icons/mdi/chevron-down";
const props = defineProps({
modelValue: {

View file

@ -1,6 +1,9 @@
<script setup lang="ts">
import { ViewType } from "~~/composables/use-preferences";
import { ItemSummary } from "~~/lib/api/types/data-contracts";
import MdiDotsVertical from "~icons/mdi/dots-vertical";
import MdiCardTextOutline from "~icons/mdi/card-text-outline";
import MdiTable from "~icons/mdi/table";
type Props = {
view?: ViewType;
@ -30,18 +33,18 @@
<template #description>
<div v-if="!viewSet" class="dropdown dropdown-hover dropdown-left">
<label tabindex="0" class="btn btn-ghost m-1">
<Icon name="mdi-dots-vertical" class="h-7 w-7" />
<MdiDotsVertical class="h-7 w-7" />
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-32">
<li>
<button @click="setViewPreference('card')">
<Icon name="mdi-card-text-outline" class="h-5 w-5" />
<MdiCardTextOutline class="h-5 w-5" />
{{ $t("item.selectable.card") }}
</button>
</li>
<li>
<button @click="setViewPreference('table')">
<Icon name="mdi-table" class="h-5 w-5" />
<MdiTable class="h-5 w-5" />
{{ $t("item.selectable.table") }}
</button>
</li>

View file

@ -19,10 +19,13 @@
>
<template v-if="typeof h === 'string'">{{ h }}</template>
<template v-else>{{ h.text }}</template>
<div :class="`inline-flex ${sortByProperty === h.value ? '' : 'opacity-0'}`">
<div
v-if="sortByProperty === h.value"
:class="`inline-flex ${sortByProperty === h.value ? '' : 'opacity-0'}`"
>
<span class="swap swap-rotate" :class="{ 'swap-active': pagination.descending }">
<Icon name="mdi-arrow-down" class="swap-on h-5 w-5" />
<Icon name="mdi-arrow-up" class="swap-off h-5 w-5" />
<MdiArrowDown class="swap-on h-5 w-5" />
<MdiArrowUp class="swap-off h-5 w-5" />
</span>
</div>
</div>
@ -50,8 +53,8 @@
<Currency :amount="d.purchasePrice" />
</template>
<template v-else-if="cell(h) === 'cell-insured'">
<Icon v-if="d.insured" name="mdi-check" class="text-green-500 h-5 w-5" />
<Icon v-else name="mdi-close" class="text-red-500 h-5 w-5" />
<MdiCheck v-if="d.insured" class="text-green-500 h-5 w-5 inline" />
<MdiClose v-else class="text-red-500 h-5 w-5 inline" />
</template>
<slot v-else :name="cell(h)" v-bind="{ item: d }">
{{ extractValue(d, h.value) }}
@ -73,6 +76,10 @@
<script setup lang="ts">
import { TableData, TableHeader } from "./Table.types";
import { ItemSummary } from "~~/lib/api/types/data-contracts";
import MdiArrowDown from "~icons/mdi/arrow-down";
import MdiArrowUp from "~icons/mdi/arrow-up";
import MdiCheck from "~icons/mdi/check";
import MdiClose from "~icons/mdi/close";
type Props = {
items: ItemSummary[];

View file

@ -1,5 +1,7 @@
<script setup lang="ts">
import { LabelOut, LabelSummary } from "~~/lib/api/types/data-contracts";
import MdiArrowRight from "~icons/mdi/arrow-right";
import MdiTagOutline from "~icons/mdi/tag-outline";
export type sizes = "sm" | "md" | "lg" | "xl";
defineProps({
@ -32,8 +34,8 @@
:to="`/label/${label.id}`"
>
<label class="swap swap-rotate" :class="isActive ? 'swap-active' : ''">
<Icon name="heroicons-arrow-right" class="mr-2 swap-on"></Icon>
<Icon name="heroicons-tag" class="mr-2 swap-off"></Icon>
<MdiArrowRight class="mr-2 swap-on" />
<MdiTagOutline class="mr-2 swap-off" />
</label>
{{ label.name }}
</NuxtLink>

View file

@ -15,7 +15,7 @@
<BaseButton class="rounded-r-none" :loading="loading" type="submit"> {{ $t("label.create.button1") }} </BaseButton>
<div class="dropdown dropdown-top">
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
<Icon class="h-5 w-5" name="mdi-chevron-down" />
<MdiChevronDown class="h-5 w-5" />
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-64 right-0">
<li>
@ -33,6 +33,7 @@
</template>
<script setup lang="ts">
import MdiChevronDown from "~icons/mdi/chevron-down";
const props = defineProps({
modelValue: {
type: Boolean,

View file

@ -13,18 +13,13 @@
>
<h2 class="flex items-center justify-between gap-2">
<label class="swap swap-rotate" :class="isActive ? 'swap-active' : ''">
<Icon name="heroicons-arrow-right" class="swap-on h-6 w-6" />
<Icon name="heroicons-map-pin" class="swap-off h-6 w-6" />
<MdiArrowRight class="swap-on h-6 w-6" />
<MdiMapMarkerOutline class="swap-off h-6 w-6" />
</label>
<span class="mx-auto">
{{ location.name }}
</span>
<span
class="badge badge-primary h-6 badge-lg"
:class="{
'opacity-0': !hasCount,
}"
>
<span class="badge badge-primary h-6 badge-lg" :class="{ 'opacity-0': !hasCount }">
{{ count }}
</span>
</h2>
@ -34,6 +29,8 @@
<script lang="ts" setup>
import { LocationOut, LocationOutCount, LocationSummary } from "~~/lib/api/types/data-contracts";
import MdiArrowRight from "~icons/mdi/arrow-right";
import MdiMapMarkerOutline from "~icons/mdi/map-marker-outline";
const props = defineProps({
location: {

View file

@ -16,7 +16,7 @@
<BaseButton class="rounded-r-none" type="submit" :loading="loading"> {{ $t("location.create.button1") }} </BaseButton>
<div class="dropdown dropdown-top">
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
<Icon class="h-5 w-5" name="mdi-chevron-down" />
<MdiChevronDown class="h-5 w-5" />
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-64 right-0">
<li>
@ -35,6 +35,7 @@
<script setup lang="ts">
import { LocationSummary } from "~~/lib/api/types/data-contracts";
import MdiChevronDown from "~icons/mdi/chevron-down";
const props = defineProps({
modelValue: {
type: Boolean,

View file

@ -14,7 +14,7 @@
v-if="selected"
:class="['absolute inset-y-0 right-0 flex items-center pr-4', active ? 'text-white' : 'text-primary']"
>
<Icon name="mdi-check" class="h-5 w-5" aria-hidden="true" />
<MdiCheck class="h-5 w-5" aria-hidden="true" />
</span>
</div>
<div v-if="cast(item.value).name != cast(item.value).treeString" class="text-xs mt-1">
@ -28,6 +28,7 @@
<script lang="ts" setup>
import { FlatTreeItem, useFlatLocations } from "~~/composables/use-location-helpers";
import { LocationSummary } from "~~/lib/api/types/data-contracts";
import MdiCheck from "~icons/mdi/check";
type Props = {
modelValue?: LocationSummary | null;
@ -43,7 +44,7 @@
const props = defineProps<Props>();
const value = useVModel(props, "modelValue");
const locations = await useFlatLocations();
const locations = useFlatLocations();
const form = ref({
parent: null as LocationSummary | null,
search: "",

View file

@ -1,6 +1,10 @@
<script setup lang="ts">
import { useTreeState } from "./tree-state";
import { TreeItem } from "~~/lib/api/types/data-contracts";
import MdiChevronDown from "~icons/mdi/chevron-down";
import MdiChevronRight from "~icons/mdi/chevron-right";
import MdiMapMarker from "~icons/mdi/map-marker";
import MdiPackageVariant from "~icons/mdi/package-variant";
type Props = {
treeId: string;
@ -56,12 +60,12 @@
'swap-active': openRef,
}"
>
<Icon name="mdi-chevron-right" class="h-6 w-6 swap-off" />
<Icon name="mdi-chevron-down" class="h-6 w-6 swap-on" />
<MdiChevronRight name="mdi-chevron-right" class="h-6 w-6 swap-off" />
<MdiChevronDown name="mdi-chevron-down" class="h-6 w-6 swap-on" />
</label>
</div>
<Icon v-if="item.type === 'location'" name="mdi-map-marker" class="h-4 w-4" />
<Icon v-else name="mdi-package-variant" class="h-4 w-4" />
<MdiMapMarker v-if="item.type === 'location'" class="h-4 w-4" />
<MdiPackageVariant v-else class="h-4 w-4" />
<NuxtLink class="hover:link text-lg" :to="link" @click.stop>{{ item.name }} </NuxtLink>
</div>
<div v-if="openRef" class="ml-4">

View file

@ -1,7 +1,7 @@
<template>
<div ref="el" class="dropdown" :class="{ 'dropdown-open': dropdownOpen }">
<button ref="btn" tabindex="0" class="btn btn-xs" @click="toggle">
{{ label }} {{ len }} <Icon name="mdi-chevron-down" class="h-4 w-4" />
{{ label }} {{ len }} <MdiChevronDown class="h-4 w-4" />
</button>
<div tabindex="0" class="dropdown-content mt-1 w-64 shadow bg-base-100 rounded-md">
<div class="pt-4 px-4 shadow-sm mb-1">
@ -39,6 +39,7 @@
</template>
<script setup lang="ts">
import MdiChevronDown from "~icons/mdi/chevron-down";
type Props = {
label: string;
options: any[];

View file

@ -6,17 +6,15 @@
'swap-active': copied,
}"
>
<Icon
<MdiContentCopy
class="swap-off"
name="mdi-content-copy"
:style="{
height: `${iconSize}px`,
width: `${iconSize}px`,
}"
/>
<Icon
<MdiClipboard
class="swap-on"
name="mdi-clipboard"
:style="{
height: `${iconSize}px`,
width: `${iconSize}px`,
@ -27,6 +25,9 @@
</template>
<script setup lang="ts">
import MdiContentCopy from "~icons/mdi/content-copy";
import MdiClipboard from "~icons/mdi/clipboard";
const props = defineProps({
text: {
type: String as () => string,
@ -51,5 +52,3 @@
}, 1000);
}
</script>
<style scoped></style>

View file

@ -22,6 +22,6 @@
return "";
}
return fmtDate(props.date, props.format, props.datetimeType);
return fmtDate(props.date, props.format);
});
</script>

View file

@ -16,7 +16,7 @@
<template v-else-if="detail.type === 'link'">
<div class="tooltip tooltip-primary tooltip-right" :data-tip="detail.href">
<a class="btn btn-primary btn-xs" :href="detail.href" target="_blank">
<Icon name="mdi-open-in-new" class="mr-2 swap-on"></Icon>
<MdiOpenInNew class="mr-2 swap-on" />
{{ detail.text }}
</a>
</div>
@ -51,6 +51,7 @@
<script setup lang="ts">
import type { AnyDetail, Detail } from "./types";
import MdiOpenInNew from "~icons/mdi/open-in-new";
defineProps({
details: {

View file

@ -2,7 +2,7 @@
<div class="dropdown dropdown-left">
<slot>
<label tabindex="0" class="btn btn-circle btn-sm">
<Icon name="mdi-qrcode" />
<MdiQrcode />
</label>
</slot>
<div tabindex="0" class="card compact dropdown-content shadow-lg bg-base-100 rounded-box w-64">
@ -16,6 +16,7 @@
<script setup lang="ts">
import { route } from "../../lib/api/base";
import MdiQrcode from "~icons/mdi/qrcode";
function getQRCodeUrl(): string {
const currentURL = window.location.href;

View file

@ -37,7 +37,7 @@ function ordinalIndicator(num: number) {
}
}
export function fmtDate(value: string | Date, fmt: DateTimeFormat = "human", fmtType: DateTimeType): string {
export function fmtDate(value: string | Date, fmt: DateTimeFormat = "human"): string {
const months = [
"January",
"February",
@ -62,11 +62,6 @@ export function fmtDate(value: string | Date, fmt: DateTimeFormat = "human", fmt
return "";
}
if (fmtType === "date") {
// Offset local time
dt.setHours(dt.getHours() + dt.getTimezoneOffset() / 60);
}
switch (fmt) {
case "relative":
return useTimeAgo(dt).value + useDateFormat(dt, " (YYYY-MM-DD)").value;

View file

@ -7,8 +7,8 @@ export interface FlatTreeItem {
treeString: string;
}
export function flatTree(tree: TreeItem[]): Ref<FlatTreeItem[]> {
const v = ref<FlatTreeItem[]>([]);
function flatTree(tree: TreeItem[]): FlatTreeItem[] {
const v = [] as FlatTreeItem[];
// turns the nested items into a flat items array where
// the display is a string of the tree hierarchy separated by breadcrumbs
@ -19,7 +19,7 @@ export function flatTree(tree: TreeItem[]): Ref<FlatTreeItem[]> {
}
for (const item of items) {
v.value.push({
v.push({
id: item.id,
name: item.name,
treeString: display + item.name,
@ -35,14 +35,18 @@ export function flatTree(tree: TreeItem[]): Ref<FlatTreeItem[]> {
return v;
}
export async function useFlatLocations(): Promise<Ref<FlatTreeItem[]>> {
const api = useUserApi();
export function useFlatLocations(): Ref<FlatTreeItem[]> {
const locations = useLocationStore();
const locations = await api.locations.getTree();
if (!locations) {
return ref([]);
if (locations.tree === null) {
locations.refreshTree();
}
return flatTree(locations.data);
return computed(() => {
if (locations.tree === null) {
return [];
}
return flatTree(locations.tree);
});
}

View file

@ -38,3 +38,27 @@ export function useTheme(): UseTheme {
return { theme, setTheme };
}
export function useIsDark() {
const theme = useTheme();
const darkthemes = [
"synthwave",
"retro",
"cyberpunk",
"valentine",
"halloween",
"forest",
"aqua",
"black",
"luxury",
"dracula",
"business",
"night",
"coffee",
];
return computed(() => {
return darkthemes.includes(theme.theme.value);
});
}

1
frontend/global.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="unplugin-icons/types/vue" />

View file

@ -17,7 +17,7 @@
<!-- Button -->
<div class="navbar z-[99] lg:hidden top-0 fixed bg-primary shadow-md drawer-button">
<label for="my-drawer-2" class="btn btn-square btn-ghost text-base-100 drawer-button lg:hidden">
<Icon name="mdi-menu" class="h-6 w-6" />
<MdiMenu class="h-6 w-6" />
</label>
<NuxtLink to="/home">
<h2 class="text-3xl font-bold tracking-tight text-base-100 flex">
@ -51,7 +51,7 @@
<div class="dropdown overflow visible w-40">
<label tabindex="0" class="btn btn-primary btn-block text-lg text-no-transform">
<span>
<Icon name="mdi-plus" class="mr-1 -ml-1" />
<MdiPlus class="mr-1 -ml-1" />
</span>
{{ $t("default.create") }}
</label>
@ -74,7 +74,7 @@
'bg-secondary text-secondary-content': n.active?.value,
}"
>
<Icon :name="n.icon" class="h-6 w-6 mr-4" />
<component :is="n.icon" class="h-6 w-6 mr-4" />
{{ $t(n.name) }}
</NuxtLink>
</li>
@ -95,6 +95,14 @@
<script lang="ts" setup>
import { useLabelStore } from "~~/stores/labels";
import { useLocationStore } from "~~/stores/locations";
import MdiMenu from "~icons/mdi/menu";
import MdiPlus from "~icons/mdi/plus";
import MdiHome from "~icons/mdi/home";
import MdiFileTree from "~icons/mdi/file-tree";
import MdiMagnify from "~icons/mdi/magnify";
import MdiAccount from "~icons/mdi/account";
import MdiCog from "~icons/mdi/cog";
const username = computed(() => authCtx.user?.name || "User");
@ -140,35 +148,35 @@
const nav = [
{
icon: "mdi-home",
icon: MdiHome,
active: computed(() => route.path === "/home"),
id: 0,
name: "default.nav.home",
to: "/home",
},
{
icon: "mdi-file-tree",
icon: MdiFileTree,
id: 4,
active: computed(() => route.path === "/locations"),
name: "default.nav.locations",
to: "/locations",
},
{
icon: "mdi-magnify",
icon: MdiMagnify,
id: 3,
active: computed(() => route.path === "/items"),
name: "default.nav.search",
to: "/items",
},
{
icon: "mdi-account",
icon: MdiAccount,
id: 1,
active: computed(() => route.path === "/profile"),
name: "default.nav.profile",
to: "/profile",
},
{
icon: "mdi-cog",
icon: MdiCog,
id: 6,
active: computed(() => route.path === "/tools"),
name: "default.nav.tools",
@ -187,6 +195,7 @@
onServerEvent(ServerEvent.LocationMutation, () => {
locationStore.refreshChildren();
locationStore.refreshParents();
locationStore.refreshTree();
});
onServerEvent(ServerEvent.ItemMutation, () => {

View file

@ -0,0 +1,43 @@
import { describe, test, expect } from "vitest";
import { format, zeroTime, factorRange, parse } from "./datelib";
describe("format", () => {
test("should format a date as a string", () => {
const date = new Date(2020, 1, 1);
expect(format(date)).toBe("2020-02-01");
});
test("should return the string if a string is passed in", () => {
expect(format("2020-02-01")).toBe("2020-02-01");
});
});
describe("zeroTime", () => {
test("should zero out the time", () => {
const date = new Date(2020, 1, 1, 12, 30, 30);
const zeroed = zeroTime(date);
expect(zeroed.getHours()).toBe(0);
expect(zeroed.getMinutes()).toBe(0);
expect(zeroed.getSeconds()).toBe(0);
});
});
describe("factorRange", () => {
test("should return a range of dates", () => {
const [start, end] = factorRange(10);
// Start should be today
expect(start).toBeInstanceOf(Date);
expect(start.getFullYear()).toBe(new Date().getFullYear());
// End should be 10 days from now
expect(end).toBeInstanceOf(Date);
expect(end.getFullYear()).toBe(new Date().getFullYear());
});
});
describe("parse", () => {
test("should parse a date string", () => {
const date = parse("2020-02-01");
expect(date).toBeInstanceOf(Date);
});
});

View file

@ -0,0 +1,34 @@
import { addDays } from "date-fns";
/*
* Formats a date as a string
* */
export function format(date: Date | string): string {
if (typeof date === "string") {
return date;
}
return date.toISOString().split("T")[0];
}
export function zeroTime(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}
export function factorRange(offset: number = 7): [Date, Date] {
const date = zeroTime(new Date());
return [date, addDays(date, offset)];
}
export function factory(offset = 0): Date {
if (offset) {
return addDays(zeroTime(new Date()), offset);
}
return zeroTime(new Date());
}
export function parse(yyyyMMdd: string): Date {
const parts = yyyyMMdd.split("-");
return new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
}

View file

@ -10,6 +10,7 @@ export default defineNuxtConfig({
"@vueuse/nuxt",
"@vite-pwa/nuxt",
"./nuxt.proxyoverride.ts",
"unplugin-icons/nuxt",
],
nitro: {
devProxy: {

View file

@ -15,6 +15,7 @@
},
"devDependencies": {
"@faker-js/faker": "^8.0.0",
"@iconify-json/mdi": "^1.1.64",
"@nuxtjs/eslint-config-typescript": "^12.0.0",
"@nuxtjs/i18n": "^8.1.1",
"@types/dompurify": "^3.0.0",
@ -31,24 +32,28 @@
"nuxt": "3.6.5",
"prettier": "^2.7.1",
"typescript": "^5.0.0",
"unplugin-icons": "^0.18.5",
"vite-plugin-eslint": "^1.8.1",
"vitest": "^1.0.0"
},
"dependencies": {
"@headlessui/vue": "^1.7.9",
"@iconify/vue": "^3.2.1",
"@nuxtjs/tailwindcss": "^6.1.3",
"@pinia/nuxt": "^0.5.0",
"@tailwindcss/aspect-ratio": "^0.4.0",
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.4",
"@types/lunr": "^2.3.7",
"@vuepic/vue-datepicker": "^8.1.1",
"@vueuse/nuxt": "^10.0.0",
"@vueuse/router": "^10.0.0",
"autoprefixer": "^10.4.8",
"daisyui": "^2.24.0",
"date-fns": "^3.3.1",
"dompurify": "^3.0.0",
"h3": "^1.7.1",
"http-proxy": "^1.18.1",
"lunr": "^2.3.9",
"markdown-it": "^14.0.0",
"pinia": "^2.0.21",
"postcss": "^8.4.16",

View file

@ -1,4 +1,14 @@
<script setup lang="ts">
import MdiGithub from "~icons/mdi/github";
import MdiTwitter from "~icons/mdi/twitter";
import MdiDiscord from "~icons/mdi/discord";
import MdiFolder from "~icons/mdi/folder";
import MdiAccount from "~icons/mdi/account";
import MdiAccountPlus from "~icons/mdi/account-plus";
import MdiLogin from "~icons/mdi/login";
import MdiArrowRight from "~icons/mdi/arrow-right";
import MdiLock from "~icons/mdi/lock";
useHead({
title: "Homebox | Organize and Tag Your Stuff",
});
@ -147,16 +157,16 @@
</div>
<div class="flex mt-6 sm:mt-0 gap-4 ml-auto text-neutral-content">
<a class="tooltip" data-tip="Project Github" href="https://github.com/hay-kot/homebox" target="_blank">
<Icon name="mdi-github" class="h-8 w-8" />
<MdiGithub class="h-8 w-8" />
</a>
<a href="https://twitter.com/haybytes" class="tooltip" data-tip="Follow The Developer" target="_blank">
<Icon name="mdi-twitter" class="h-8 w-8" />
<MdiTwitter class="h-8 w-8" />
</a>
<a href="https://discord.gg/tuncmNrE4z" class="tooltip" data-tip="Join The Discord" target="_blank">
<Icon name="mdi-discord" class="h-8 w-8" />
<MdiDiscord class="h-8 w-8" />
</a>
<a href="https://hay-kot.github.io/homebox/" class="tooltip" data-tip="Read The Docs" target="_blank">
<Icon name="mdi-folder" class="h-8 w-8" />
<MdiFolder class="h-8 w-8" />
</a>
<a
v-for="locale in availableLocales"
@ -175,7 +185,7 @@
<div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl align-center">
<Icon name="heroicons-user" class="mr-1 w-7 h-7" />
<MdiAccount class="mr-1 w-7 h-7" />
{{ $t("index.register") }}
</h2>
<FormTextField v-model="email" :label="$t('index.register_email')" />
@ -205,7 +215,7 @@
<div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl align-center">
<Icon name="heroicons-user" class="mr-1 w-7 h-7" />
<MdiAccount class="mr-1 w-7 h-7" />
{{ $t("index.login") }}
</h2>
<template v-if="status && status.demo">
@ -239,14 +249,14 @@
@click="() => toggleLogin()"
>
<template #icon>
<Icon v-if="!registerForm" name="mdi-account-plus-outline" class="w-5 h-5 swap-off" />
<Icon v-else name="mdi-login" class="w-5 h-5 swap-off" />
<Icon name="mdi-arrow-right" class="w-5 h-5 swap-on" />
<MdiAccountPlus v-if="!registerForm" class="w-5 h-5 swap-off" />
<MdiLogin v-else class="w-5 h-5 swap-off" />
<MdiArrowRight class="w-5 h-5 swap-on" />
</template>
{{ registerForm ? $t("index.login") : $t("index.register") }}
</BaseButton>
<p v-else class="text-base-content italic text-sm inline-flex items-center gap-2">
<Icon name="mdi-lock" class="w-4 h-4 inline-block" />
<MdiLock class="w-4 h-4 inline-block" />
{{ $t("index.refus") }}
</p>
</div>

View file

@ -1,6 +1,11 @@
<script setup lang="ts">
import { AnyDetail, Detail, Details, filterZeroValues } from "~~/components/global/DetailsSection/types";
import { ItemAttachment } from "~~/lib/api/types/data-contracts";
import MdiClose from "~icons/mdi/close";
import MdiPackageVariant from "~icons/mdi/package-variant";
import MdiPlus from "~icons/mdi/plus";
import MdiMinus from "~icons/mdi/minus";
import MdiDownload from "~icons/mdi/download";
definePageMeta({
middleware: ["auth"],
@ -439,10 +444,10 @@
<div ref="refDialogBody" class="relative">
<div class="absolute right-0 -mt-3 -mr-3 sm:-mt-4 sm:-mr-4 space-x-1">
<a class="btn btn-sm sm:btn-md btn-primary btn-circle" :href="dialoged.src" download>
<Icon class="h-5 w-5" name="mdi-download" />
<MdiDownload class="h-5 w-5" />
</a>
<button class="btn btn-sm sm:btn-md btn-primary btn-circle" @click="closeDialog()">
<Icon class="h-5 w-5" name="mdi-close" />
<MdiClose class="h-5 w-5" />
</button>
</div>
@ -456,7 +461,7 @@
<div class="flex flex-wrap items-end gap-2">
<div class="avatar placeholder mb-auto">
<div class="bg-neutral-focus text-neutral-content rounded-full w-12">
<Icon name="mdi-package-variant" class="h-7 w-7" />
<MdiPackageVariant class="h-7 w-7" />
</div>
</div>
<div>
@ -525,10 +530,10 @@
class="opacity-0 group-hover:opacity-100 ml-4 my-0 duration-75 transition-opacity inline-flex gap-2"
>
<button class="btn btn-circle btn-xs" @click="adjustQuantity(-1)">
<Icon name="mdi-minus" class="h-3 w-3" />
<MdiMinus class="h-3 w-3" />
</button>
<button class="btn btn-circle btn-xs" @click="adjustQuantity(1)">
<Icon name="mdi-plus" class="h-3 w-3" />
<MdiPlus class="h-3 w-3" />
</button>
</span>
</template>

View file

@ -5,6 +5,9 @@
import { useLocationStore } from "~~/stores/locations";
import { capitalize } from "~~/lib/strings";
import Autocomplete from "~~/components/Form/Autocomplete.vue";
import MdiDelete from "~icons/mdi/delete";
import MdiPencil from "~icons/mdi/pencil";
import MdiContentSaveOutline from "~icons/mdi/content-save-outline";
definePageMeta({
middleware: ["auth"],
@ -444,12 +447,12 @@
</div>
<BaseButton size="sm" @click="saveItem">
<template #icon>
<Icon name="mdi-content-save-outline" />
<MdiContentSaveOutline />
</template>
{{ $t("item.edit.save_button") }}
</BaseButton>
<BaseButton class="btn btn-sm btn-error" @click="deleteItem()">
<Icon name="mdi-delete" class="mr-2" />
<MdiDelete class="mr-2" />
{{ $t("item.edit.delete_button") }}
</BaseButton>
</div>
@ -521,7 +524,7 @@
<FormTextField v-model="field.textValue" :label="$t('item.edit.custom.value')" />
<div class="tooltip" data-tip="Delete">
<button class="btn btn-sm btn-square mb-2 ml-2" @click="item.fields.splice(idx, 1)">
<Icon name="mdi-delete" />
<MdiDelete />
</button>
</div>
</div>
@ -575,12 +578,12 @@
<div class="flex gap-2 justify-end">
<div class="tooltip" data-tip="Delete">
<button class="btn btn-sm btn-square" @click="deleteAttachment(attachment.id)">
<Icon name="mdi-delete" />
<MdiDelete />
</button>
</div>
<div class="tooltip" data-tip="Edit">
<button class="btn btn-sm btn-square" @click="openAttachmentEditDialog(attachment)">
<Icon name="mdi-pencil" />
<MdiPencil />
</button>
</div>
</div>

View file

@ -2,6 +2,13 @@
import DatePicker from "~~/components/Form/DatePicker.vue";
import { StatsFormat } from "~~/components/global/StatCard/types";
import { ItemOut, MaintenanceEntry } from "~~/lib/api/types/data-contracts";
import MdiPost from "~icons/mdi/post";
import MdiPlus from "~icons/mdi/plus";
import MdiCheck from "~icons/mdi/check";
import MdiDelete from "~icons/mdi/delete";
import MdiEdit from "~icons/mdi/edit";
import MdiCalendar from "~icons/mdi/calendar";
import MdiWrenchClock from "~icons/mdi/wrench-clock";
const props = defineProps<{
item: ItemOut;
@ -184,7 +191,7 @@
<div class="py-2 flex justify-end">
<BaseButton type="submit" class="ml-2 mt-2">
<template #icon>
<Icon name="mdi-post" />
<MdiPost />
</template>
{{ entry.id ? "Update" : "Create" }}
</BaseButton>
@ -214,7 +221,7 @@
</div>
<BaseButton class="ml-auto" size="sm" @click="newEntry()">
<template #icon>
<Icon name="mdi-plus" />
<MdiPlus />
</template>
New
</BaseButton>
@ -228,11 +235,11 @@
<template #description>
<div class="flex flex-wrap gap-2">
<div v-if="validDate(e.completedDate)" class="badge p-3">
<Icon name="mdi-check" class="mr-2" />
<MdiCheck class="mr-2" />
<DateTime :date="e.completedDate" format="human" datetime-type="date" />
</div>
<div v-else-if="validDate(e.scheduledDate)" class="badge p-3">
<Icon name="mdi-calendar" class="mr-2" />
<MdiCalendar class="mr-2" />
<DateTime :date="e.scheduledDate" format="human" datetime-type="date" />
</div>
<div class="tooltip tooltip-primary" data-tip="Cost">
@ -249,13 +256,13 @@
<div class="flex justify-end p-4 gap-1">
<BaseButton size="sm" @click="openEditDialog(e)">
<template #icon>
<Icon name="mdi-edit" />
<MdiEdit />
</template>
Edit
</BaseButton>
<BaseButton size="sm" @click="deleteEntry(e.id)">
<template #icon>
<Icon name="mdi-delete" />
<MdiDelete />
</template>
Delete
</BaseButton>
@ -267,7 +274,7 @@
class="relative block w-full rounded-lg border-2 border-dashed border-base-content p-12 text-center"
@click="newEntry()"
>
<Icon name="mdi-wrench-clock" class="h-16 w-16"></Icon>
<MdiWrenchClock class="h-16 w-16 inline" />
<span class="mt-2 block text-sm font-medium text-gray-900"> Create Your First Entry </span>
</button>
</div>

View file

@ -2,6 +2,11 @@
import { ItemSummary, LabelSummary, LocationOutCount } from "~~/lib/api/types/data-contracts";
import { useLabelStore } from "~~/stores/labels";
import { useLocationStore } from "~~/stores/locations";
import MdiLoading from "~icons/mdi/loading";
import MdiMagnify from "~icons/mdi/magnify";
import MdiDelete from "~icons/mdi/delete";
import MdiChevronRight from "~icons/mdi/chevron-right";
import MdiChevronLeft from "~icons/mdi/chevron-left";
definePageMeta({
middleware: ["auth"],
@ -319,8 +324,8 @@
</div>
<BaseButton class="btn-block md:w-auto" @click.prevent="submit">
<template #icon>
<Icon v-if="loading" name="mdi-loading" class="animate-spin" />
<Icon v-else name="mdi-search" />
<MdiLoading v-if="loading" class="animate-spin" />
<MdiMagnify v-else />
</template>
{{ $t("items.query.search") }}
</BaseButton>
@ -410,7 +415,7 @@
class="btn btn-square btn-sm md:ml-0 ml-auto mt-auto mb-2"
@click="fieldTuples.splice(idx, 1)"
>
<Icon name="mdi-trash" class="w-5 h-5" />
<MdiDelete class="w-5 h-5" />
</button>
</div>
<BaseButton type="button" class="btn-sm mt-2" @click="() => fieldTuples.push(['', ''])"> {{ $t("items.query.fieldSelector_dropdown.add") }} </BaseButton>
@ -433,14 +438,14 @@
<div class="flex">
<div class="btn-group">
<button :disabled="!hasPrev" class="btn text-no-transform" @click="prev">
<Icon class="mr-1 h-6 w-6" name="mdi-chevron-left" />
<MdiChevronLeft class="mr-1 h-6 w-6" name="mdi-chevron-left" />
{{ $t("items.result.prev") }}
</button>
<button v-if="hasPrev" class="btn text-no-transform" @click="page = 1">{{ $t("items.result.first") }}</button>
<button v-if="hasNext" class="btn text-no-transform" @click="page = totalPages">{{ $t("items.result.last") }}</button>
<button :disabled="!hasNext" class="btn text-no-transform" @click="next">
{{ $t("items.result.next") }}
<Icon class="ml-1 h-6 w-6" name="mdi-chevron-right" />
<MdiChevronRight class="ml-1 h-6 w-6" name="mdi-chevron-right" />
</button>
</div>
</div>

View file

@ -1,4 +1,8 @@
<script setup lang="ts">
import MdiPackageVariant from "~icons/mdi/package-variant";
import MdiPencil from "~icons/mdi/pencil";
import MdiDelete from "~icons/mdi/delete";
definePageMeta({
middleware: ["auth"],
});
@ -107,7 +111,7 @@
<div class="flex flex-wrap items-end gap-2">
<div class="avatar placeholder mb-auto">
<div class="bg-neutral-focus text-neutral-content rounded-full w-12">
<Icon name="mdi-package-variant" class="h-7 w-7" />
<MdiPackageVariant class="h-7 w-7" />
</div>
</div>
<div>
@ -125,12 +129,12 @@
<div class="btn-group">
<PageQRCode class="dropdown-left" />
<BaseButton size="sm" @click="openUpdate">
<Icon class="mr-1" name="mdi-pencil" />
<MdiPencil class="mr-1" />
{{ $t("label.edit.edit_button") }}
</BaseButton>
</div>
<BaseButton class="btn btn-sm" @click="confirmDelete()">
<Icon name="mdi-delete" class="mr-2" />
<MdiDelete class="mr-2" />
{{ $t("label.edit.delete_button") }}
</BaseButton>
</div>

View file

@ -1,6 +1,9 @@
<script setup lang="ts">
import { LocationSummary, LocationUpdate } from "~~/lib/api/types/data-contracts";
import { useLocationStore } from "~~/stores/locations";
import MdiPackageVariant from "~icons/mdi/package-variant";
import MdiPencil from "~icons/mdi/pencil";
import MdiDelete from "~icons/mdi/delete";
definePageMeta({
middleware: ["auth"],
@ -123,7 +126,7 @@
<div class="flex flex-wrap items-end gap-2">
<div class="avatar placeholder mb-auto">
<div class="bg-neutral-focus text-neutral-content rounded-full w-12">
<Icon name="mdi-package-variant" class="h-7 w-7" />
<MdiPackageVariant name="mdi-package-variant" class="h-7 w-7" />
</div>
</div>
<div>
@ -149,12 +152,12 @@
<div class="btn-group">
<PageQRCode class="dropdown-left" />
<BaseButton size="sm" @click="openUpdate">
<Icon class="mr-1" name="mdi-pencil" />
<MdiPencil class="mr-1" name="mdi-pencil" />
{{ $t("location.edit.edit_button") }}
</BaseButton>
</div>
<BaseButton class="btn btn-sm" @click="confirmDelete()">
<Icon name="mdi-delete" class="mr-2" />
<MdiDelete name="mdi-delete" class="mr-2" />
{{ $t("location.edit.delete_button") }}
</BaseButton>
</div>

View file

@ -1,5 +1,6 @@
<script setup lang="ts">
import { useTreeState } from "~~/components/Location/Tree/tree-state";
import MdiCollapseAllOutline from "~icons/mdi/collapse-all-outline";
definePageMeta({
middleware: ["auth"],
@ -67,7 +68,7 @@
<div class="flex justify-end mb-2">
<div class="btn-group">
<button class="btn btn-sm tooltip tooltip-top" data-tip="Collapse Tree" @click="closeAll">
<Icon name="mdi-collapse-all-outline" />
<MdiCollapseAllOutline />
</button>
</div>
</div>

View file

@ -2,6 +2,12 @@
import { Detail } from "~~/components/global/DetailsSection/types";
import { themes } from "~~/lib/data/themes";
import { CurrenciesCurrency, NotifierCreate, NotifierOut } from "~~/lib/api/types/data-contracts";
import MdiAccount from "~icons/mdi/account";
import MdiMegaphone from "~icons/mdi/megaphone";
import MdiDelete from "~icons/mdi/delete";
import MdiFill from "~icons/mdi/fill";
import MdiPencil from "~icons/mdi/pencil";
import MdiAccountMultiple from "~icons/mdi/account-multiple";
definePageMeta({
middleware: ["auth"],
@ -347,7 +353,7 @@
<BaseCard>
<template #title>
<BaseSectionHeader>
<Icon name="mdi-account" class="mr-2 -mt-1 text-base-600" />
<MdiAccount class="mr-2 -mt-1 text-base-600" />
<span class="text-base-600"> {{ $t("profile.user.title") }} </span>
<template #description> {{ $t("profile.user.desp") }} </template>
</BaseSectionHeader>
@ -374,7 +380,7 @@
<BaseCard>
<template #title>
<BaseSectionHeader>
<Icon name="mdi-megaphone" class="mr-2 -mt-1 text-base-600" />
<MdiMegaphone class="mr-2 -mt-1 text-base-600" />
<span class="text-base-600"> {{ $t("profile.notifier.title") }} </span>
<template #description> {{ $t("profile.notifier.desp") }} </template>
</BaseSectionHeader>
@ -387,12 +393,12 @@
<div class="flex gap-2 justify-end">
<div class="tooltip" data-tip="Delete">
<button class="btn btn-sm btn-square" @click="deleteNotifier(n.id)">
<Icon name="mdi-delete" />
<MdiDelete />
</button>
</div>
<div class="tooltip" data-tip="Edit">
<button class="btn btn-sm btn-square" @click="openNotifierDialog(n)">
<Icon name="mdi-pencil" />
<MdiPencil />
</button>
</div>
</div>
@ -418,7 +424,7 @@
<BaseCard>
<template #title>
<BaseSectionHeader class="pb-0">
<Icon name="mdi-accounts" class="mr-2 -mt-1 text-base-600" />
<MdiAccountMultiple class="mr-2 -mt-1 text-base-600" />
<span class="text-base-600"> {{ $t("profile.group.title") }} </span>
<template #description>
{{ $t("profile.group.desp") }}
@ -439,7 +445,7 @@
<BaseCard>
<template #title>
<BaseSectionHeader>
<Icon name="mdi-fill" class="mr-2 text-base-600" />
<MdiFill class="mr-2 text-base-600" />
<span class="text-base-600"> {{ $t("profile.theme.title") }} </span>
<template #description>
{{ $t("profile.theme.desp") }}
@ -489,7 +495,7 @@
<BaseCard>
<template #title>
<BaseSectionHeader>
<Icon name="mdi-delete" class="mr-2 -mt-1 text-base-600" />
<MdiDelete class="mr-2 -mt-1 text-base-600" />
<span class="text-base-600"> {{ $t("profile.account.title") }}</span>
<template #description> {{ $t("profile.account.desp") }} </template>
</BaseSectionHeader>

View file

@ -5,7 +5,7 @@
<BaseCard>
<template #title>
<BaseSectionHeader>
<Icon name="mdi-file-chart" class="mr-2 -mt-1" />
<MdiFileChart class="mr-2" />
<span> {{ $t("tools.report.title") }} </span>
<template #description> {{ $t("tools.report.desp") }} </template>
</BaseSectionHeader>
@ -16,7 +16,7 @@
{{ $t("tools.report.asset.desp") }}
<template #button>
{{ $t("tools.report.asset.button") }}
<Icon name="mdi-arrow-right" class="ml-2" />
<MdiArrowRight class="ml-2" />
</template>
</DetailAction>
<DetailAction @action="getBillOfMaterials()">
@ -29,7 +29,7 @@
<BaseCard>
<template #title>
<BaseSectionHeader>
<Icon name="mdi-database" class="mr-2 -mt-1" />
<MdiDatabase class="mr-2" />
<span> {{ $t("tools.import_export.title") }} </span>
<template #description>
{{ $t("tools.import_export.desp") }}
@ -50,7 +50,7 @@
<BaseCard>
<template #title>
<BaseSectionHeader>
<Icon name="mdi-warning" class="mr-2 -mt-1" />
<MdiAlert class="mr-2" />
<span> {{ $t("tools.inventory.title") }} </span>
<template #description>
{{ $t("tools.inventory.desp") }}
@ -85,6 +85,11 @@
</template>
<script setup lang="ts">
import MdiFileChart from "~icons/mdi/file-chart";
import MdiArrowRight from "~icons/mdi/arrow-right";
import MdiDatabase from "~icons/mdi/database";
import MdiAlert from "~icons/mdi/alert";
definePageMeta({
middleware: ["auth"],
});

4638
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,13 @@
import { defineStore } from "pinia";
import { LocationsApi } from "~~/lib/api/classes/locations";
import { LocationOutCount } from "~~/lib/api/types/data-contracts";
import { LocationOutCount, TreeItem } from "~~/lib/api/types/data-contracts";
export const useLocationStore = defineStore("locations", {
state: () => ({
parents: null as LocationOutCount[] | null,
Locations: null as LocationOutCount[] | null,
client: useUserApi(),
tree: null as TreeItem[] | null,
}),
getters: {
/**
@ -60,5 +61,14 @@ export const useLocationStore = defineStore("locations", {
this.Locations = result.data;
return result;
},
async refreshTree(): ReturnType<LocationsApi["getTree"]> {
const result = await this.client.locations.getTree();
if (result.error) {
return result;
}
this.tree = result.data;
return result;
},
},
});