mirror of
https://github.com/hay-kot/homebox.git
synced 2025-08-02 07:40:28 +00:00
Merge branch 'main' into i18n
This commit is contained in:
commit
2881d3d447
51 changed files with 3169 additions and 2134 deletions
|
@ -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 (
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
mkdocs-material==9.5.9
|
||||
mkdocs-material==9.5.12
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}>();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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: {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: "",
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -22,6 +22,6 @@
|
|||
return "";
|
||||
}
|
||||
|
||||
return fmtDate(props.date, props.format, props.datetimeType);
|
||||
return fmtDate(props.date, props.format);
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
1
frontend/global.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="unplugin-icons/types/vue" />
|
|
@ -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, () => {
|
||||
|
|
43
frontend/lib/datelib/datelib.test.ts
Normal file
43
frontend/lib/datelib/datelib.test.ts
Normal 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);
|
||||
});
|
||||
});
|
34
frontend/lib/datelib/datelib.ts
Normal file
34
frontend/lib/datelib/datelib.ts
Normal 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]));
|
||||
}
|
|
@ -10,6 +10,7 @@ export default defineNuxtConfig({
|
|||
"@vueuse/nuxt",
|
||||
"@vite-pwa/nuxt",
|
||||
"./nuxt.proxyoverride.ts",
|
||||
"unplugin-icons/nuxt",
|
||||
],
|
||||
nitro: {
|
||||
devProxy: {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
4638
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue