feat: init tools page (#271)

This commit is contained in:
Hayden 2023-02-10 19:38:50 -09:00 committed by GitHub
parent ab22ea6a25
commit 6ff2d64996
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 194 additions and 129 deletions

View file

@ -53,11 +53,16 @@
); );
function setFile(e: Event) { function setFile(e: Event) {
importCsv.value = e.target.files[0]; const result = e.target as HTMLInputElement;
if (!result.files || result.files.length === 0) {
return;
}
importCsv.value = result.files[0];
} }
function uploadCsv() { function uploadCsv() {
importRef.value.click(); importRef.value?.click();
} }
const eventBus = useEventBus(); const eventBus = useEventBus();
@ -86,5 +91,7 @@
} }
eventBus.emit(EventTypes.InvalidStores); eventBus.emit(EventTypes.InvalidStores);
toast.success("Import successful!");
} }
</script> </script>

View file

@ -0,0 +1,17 @@
<template>
<div class="grid grid-cols-1 md:grid-cols-4 gap-10 py-6">
<div class="col-span-3">
<h4 class="mb-1 text-lg font-semibold">
<slot name="title"></slot>
</h4>
<p class="text-sm">
<slot></slot>
</p>
</div>
<BaseButton class="btn-primary mt-auto" @click="$emit('action')">
<slot name="button">
<slot name="title"></slot>
</slot>
</BaseButton>
</div>
</template>

View file

@ -6,7 +6,6 @@
up the tree up the tree
--> -->
<ModalConfirm /> <ModalConfirm />
<AppImportDialog v-model="modals.import" />
<ItemCreateModal v-model="modals.item" /> <ItemCreateModal v-model="modals.item" />
<LabelCreateModal v-model="modals.label" /> <LabelCreateModal v-model="modals.label" />
<LocationCreateModal v-model="modals.location" /> <LocationCreateModal v-model="modals.location" />
@ -78,10 +77,6 @@
<Icon :name="n.icon" class="h-6 w-6 mr-4" /> <Icon :name="n.icon" class="h-6 w-6 mr-4" />
{{ n.name }} {{ n.name }}
</NuxtLink> </NuxtLink>
<button v-else class="rounded-btn" @click="n.action">
<Icon :name="n.icon" class="h-6 w-6 mr-4" />
{{ n.name }}
</button>
</li> </li>
</ul> </ul>
</div> </div>
@ -172,12 +167,11 @@
to: "/locations", to: "/locations",
}, },
{ {
icon: "mdi-database", icon: "mdi-cog",
id: 2, id: 6,
name: "Import", active: computed(() => route.path === "/tools"),
action: () => { name: "Tools",
modals.import = true; to: "/tools",
},
}, },
]; ];

View file

@ -17,11 +17,11 @@ export interface DocumentOut {
} }
export interface Group { export interface Group {
createdAt: string; createdAt: Date | string;
currency: string; currency: string;
id: string; id: string;
name: string; name: string;
updatedAt: string; updatedAt: Date | string;
} }
export interface GroupStatistics { export interface GroupStatistics {
@ -39,11 +39,11 @@ export interface GroupUpdate {
} }
export interface ItemAttachment { export interface ItemAttachment {
createdAt: string; createdAt: Date | string;
document: DocumentOut; document: DocumentOut;
id: string; id: string;
type: string; type: string;
updatedAt: string; updatedAt: Date | string;
} }
export interface ItemAttachmentUpdate { export interface ItemAttachmentUpdate {
@ -76,7 +76,7 @@ export interface ItemOut {
assetId: string; assetId: string;
attachments: ItemAttachment[]; attachments: ItemAttachment[];
children: ItemSummary[]; children: ItemSummary[];
createdAt: string; createdAt: Date | string;
description: string; description: string;
fields: ItemField[]; fields: ItemField[];
id: string; id: string;
@ -96,23 +96,23 @@ export interface ItemOut {
/** @example "0" */ /** @example "0" */
purchasePrice: string; purchasePrice: string;
/** Purchase */ /** Purchase */
purchaseTime: string; purchaseTime: Date | string;
quantity: number; quantity: number;
serialNumber: string; serialNumber: string;
soldNotes: string; soldNotes: string;
/** @example "0" */ /** @example "0" */
soldPrice: string; soldPrice: string;
/** Sold */ /** Sold */
soldTime: string; soldTime: Date | string;
soldTo: string; soldTo: string;
updatedAt: string; updatedAt: Date | string;
warrantyDetails: string; warrantyDetails: string;
warrantyExpires: string; warrantyExpires: Date | string;
} }
export interface ItemSummary { export interface ItemSummary {
archived: boolean; archived: boolean;
createdAt: string; createdAt: Date | string;
description: string; description: string;
id: string; id: string;
insured: boolean; insured: boolean;
@ -123,7 +123,7 @@ export interface ItemSummary {
/** @example "0" */ /** @example "0" */
purchasePrice: string; purchasePrice: string;
quantity: number; quantity: number;
updatedAt: string; updatedAt: Date | string;
} }
export interface ItemUpdate { export interface ItemUpdate {
@ -148,7 +148,7 @@ export interface ItemUpdate {
/** @example "0" */ /** @example "0" */
purchasePrice: string; purchasePrice: string;
/** Purchase */ /** Purchase */
purchaseTime: string; purchaseTime: Date | string;
quantity: number; quantity: number;
/** Identifications */ /** Identifications */
serialNumber: string; serialNumber: string;
@ -156,10 +156,10 @@ export interface ItemUpdate {
/** @example "0" */ /** @example "0" */
soldPrice: string; soldPrice: string;
/** Sold */ /** Sold */
soldTime: string; soldTime: Date | string;
soldTo: string; soldTo: string;
warrantyDetails: string; warrantyDetails: string;
warrantyExpires: string; warrantyExpires: Date | string;
} }
export interface LabelCreate { export interface LabelCreate {
@ -169,20 +169,20 @@ export interface LabelCreate {
} }
export interface LabelOut { export interface LabelOut {
createdAt: string; createdAt: Date | string;
description: string; description: string;
id: string; id: string;
items: ItemSummary[]; items: ItemSummary[];
name: string; name: string;
updatedAt: string; updatedAt: Date | string;
} }
export interface LabelSummary { export interface LabelSummary {
createdAt: string; createdAt: Date | string;
description: string; description: string;
id: string; id: string;
name: string; name: string;
updatedAt: string; updatedAt: Date | string;
} }
export interface LocationCreate { export interface LocationCreate {
@ -193,30 +193,30 @@ export interface LocationCreate {
export interface LocationOut { export interface LocationOut {
children: LocationSummary[]; children: LocationSummary[];
createdAt: string; createdAt: Date | string;
description: string; description: string;
id: string; id: string;
items: ItemSummary[]; items: ItemSummary[];
name: string; name: string;
parent: LocationSummary; parent: LocationSummary;
updatedAt: string; updatedAt: Date | string;
} }
export interface LocationOutCount { export interface LocationOutCount {
createdAt: string; createdAt: Date | string;
description: string; description: string;
id: string; id: string;
itemCount: number; itemCount: number;
name: string; name: string;
updatedAt: string; updatedAt: Date | string;
} }
export interface LocationSummary { export interface LocationSummary {
createdAt: string; createdAt: Date | string;
description: string; description: string;
id: string; id: string;
name: string; name: string;
updatedAt: string; updatedAt: Date | string;
} }
export interface LocationUpdate { export interface LocationUpdate {
@ -229,7 +229,7 @@ export interface LocationUpdate {
export interface MaintenanceEntry { export interface MaintenanceEntry {
/** @example "0" */ /** @example "0" */
cost: string; cost: string;
date: Date; date: Date | string;
description: string; description: string;
id: string; id: string;
name: string; name: string;
@ -238,7 +238,7 @@ export interface MaintenanceEntry {
export interface MaintenanceEntryCreate { export interface MaintenanceEntryCreate {
/** @example "0" */ /** @example "0" */
cost: string; cost: string;
date: Date; date: Date | string;
description: string; description: string;
name: string; name: string;
} }
@ -246,7 +246,7 @@ export interface MaintenanceEntryCreate {
export interface MaintenanceEntryUpdate { export interface MaintenanceEntryUpdate {
/** @example "0" */ /** @example "0" */
cost: string; cost: string;
date: Date; date: Date | string;
description: string; description: string;
name: string; name: string;
} }
@ -258,7 +258,7 @@ export interface MaintenanceLog {
itemId: string; itemId: string;
} }
export interface PaginationResultRepoItemSummary { export interface PaginationResultItemSummary {
items: ItemSummary[]; items: ItemSummary[];
page: number; page: number;
pageSize: number; pageSize: number;
@ -302,7 +302,7 @@ export interface ValueOverTime {
} }
export interface ValueOverTimeEntry { export interface ValueOverTimeEntry {
date: Date; date: Date | string;
name: string; name: string;
value: number; value: number;
} }
@ -355,13 +355,13 @@ export interface ChangePassword {
} }
export interface GroupInvitation { export interface GroupInvitation {
expiresAt: string; expiresAt: Date | string;
token: string; token: string;
uses: number; uses: number;
} }
export interface GroupInvitationCreate { export interface GroupInvitationCreate {
expiresAt: string; expiresAt: Date | string;
uses: number; uses: number;
} }
@ -371,6 +371,6 @@ export interface ItemAttachmentToken {
export interface TokenResponse { export interface TokenResponse {
attachmentToken: string; attachmentToken: string;
expiresAt: string; expiresAt: Date | string;
token: string; token: string;
} }

View file

@ -40,15 +40,22 @@
// Sync Initial Currency // Sync Initial Currency
watch(group, () => { watch(group, () => {
if (group.value) { if (!group.value) {
const found = currencies.find(c => c.code === group.value.currency); return;
if (found) { }
currency.value = found;
} // @ts-expect-error - typescript is stupid, it should know group.value is not null
const found = currencies.find(c => c.code === group.value.currency);
if (found) {
currency.value = found;
} }
}); });
async function updateGroup() { async function updateGroup() {
if (!group.value) {
return;
}
const { data, error } = await api.group.update({ const { data, error } = await api.group.update({
name: group.value.name, name: group.value.name,
currency: group.value.currency, currency: group.value.currency,
@ -161,44 +168,6 @@
passwordChange.current = ""; passwordChange.current = "";
passwordChange.loading = false; passwordChange.loading = false;
} }
async function ensureAssetIDs() {
const { isCanceled } = await confirm.open(
"Are you sure you want to ensure all assets have an ID? This will take a while and cannot be undone."
);
if (isCanceled) {
return;
}
const result = await api.actions.ensureAssetIDs();
if (result.error) {
notify.error("Failed to ensure asset IDs.");
return;
}
notify.success(`${result.data.completed} assets have been updated.`);
}
async function resetItemDateTimes() {
const { isCanceled } = await confirm.open(
"Are you sure you want to reset all date and time values? This will take a while and cannot be undone."
);
if (isCanceled) {
return;
}
const result = await api.actions.resetItemDateTimes();
if (result.error) {
notify.error("Failed to reset date and time values.");
return;
}
notify.success(`${result.data.completed} assets have been updated.`);
}
</script> </script>
<template> <template>
@ -322,46 +291,6 @@
</div> </div>
</BaseCard> </BaseCard>
<BaseCard>
<template #title>
<BaseSectionHeader>
<Icon name="mdi-warning" class="mr-2 -mt-1 text-base-600" />
<span class="text-base-600"> Actions </span>
<template #description>
Apply Actions to your inventory in bulk. These are irreversible actions. Be careful.
</template>
</BaseSectionHeader>
<div class="py-4 border-t-2 border-gray-300 space-y-8">
<div class="grid grid-cols-1 md:grid-cols-4 gap-10">
<div class="col-span-3">
<h4>Manage Asset IDs</h4>
<p class="text-sm">
Ensures that all items in your inventory have a valid asset_id field. This is done by finding the
highest current asset_id field in the database and applying the next value to each item that has an
unset asset_id field. This is done in order of the created_at field.
</p>
</div>
<BaseButton class="btn-primary mt-auto" @click="ensureAssetIDs"> Ensure Asset IDs </BaseButton>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-10">
<div class="col-span-3">
<h4>Zero Item Date Times</h4>
<p class="text-sm">
Resets the time value for all date time fields in your inventory to the beginning of the date. This is
to fix a bug that was introduced early on in the development of the site that caused the time value to
be stored with the time which caused issues with date fields displaying accurate values.
<a class="link" href="https://github.com/hay-kot/homebox/issues/236" target="_blank">
See Github Issue #236 for more details
</a>
</p>
</div>
<BaseButton class="btn-primary mt-auto" @click="resetItemDateTimes"> Zero Item Date Times </BaseButton>
</div>
</div>
</template>
</BaseCard>
<BaseCard> <BaseCard>
<template #title> <template #title>
<BaseSectionHeader> <BaseSectionHeader>

118
frontend/pages/tools.vue Normal file
View file

@ -0,0 +1,118 @@
<template>
<div>
<AppImportDialog v-model="modals.import" />
<BaseContainer class="flex flex-col gap-4 mb-6">
<BaseCard>
<template #title>
<BaseSectionHeader>
<Icon name="mdi-database" class="mr-2 -mt-1" />
<span> Import / Export </span>
<template #description>
Import and export your inventory to and from a CSV file. This is useful for migrating your inventory to a
new instance of Homebox.
</template>
</BaseSectionHeader>
<div class="border-t border-gray-300 divide-gray-300 divide-y">
<DetailAction @click="modals.import = true">
<template #title>Import Inventory</template>
Imports the standard CSV format for Homebox. This will <b>not</b> overwrite any existing items in your
inventory. It will only add new items.
</DetailAction>
<!-- <DetailAction>
<template #title>Export Inventory</template>
Exports the standard CSV format for Homebox. This will export all items in your inventory.
</DetailAction> -->
</div>
</template>
</BaseCard>
<BaseCard>
<template #title>
<BaseSectionHeader>
<Icon name="mdi-warning" class="mr-2 -mt-1" />
<span> Inventory Actions </span>
<template #description>
Apply Actions to your inventory in bulk. These are irreversible actions. <b>Be careful</b>
</template>
</BaseSectionHeader>
<div class="border-t border-gray-300 divide-gray-300 divide-y">
<DetailAction @action="ensureAssetIDs">
<template #title>Ensure Asset IDs</template>
Ensures that all items in your inventory have a valid asset_id field. This is done by finding the highest
current asset_id field in the database and applying the next value to each item that has an unset asset_id
field. This is done in order of the created_at field.
</DetailAction>
<DetailAction @click="resetItemDateTimes">
<template #title> Zero Item Date Times</template>
Resets the time value for all date time fields in your inventory to the beginning of the date. This is to
fix a bug that was introduced early on in the development of the site that caused the time value to be
stored with the time which caused issues with date fields displaying accurate values.
<a class="link" href="https://github.com/hay-kot/homebox/issues/236" target="_blank">
See Github Issue #236 for more details
</a>
</DetailAction>
</div>
</template>
</BaseCard>
</BaseContainer>
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: ["auth"],
});
useHead({
title: "Homebox | Profile",
});
const modals = ref({
item: false,
location: false,
label: false,
import: false,
});
const api = useUserApi();
const confirm = useConfirm();
const notify = useNotifier();
async function ensureAssetIDs() {
const { isCanceled } = await confirm.open(
"Are you sure you want to ensure all assets have an ID? This can take a while and cannot be undone."
);
if (isCanceled) {
return;
}
const result = await api.actions.ensureAssetIDs();
if (result.error) {
notify.error("Failed to ensure asset IDs.");
return;
}
notify.success(`${result.data.completed} assets have been updated.`);
}
async function resetItemDateTimes() {
const { isCanceled } = await confirm.open(
"Are you sure you want to reset all date and time values? This can take a while and cannot be undone."
);
if (isCanceled) {
return;
}
const result = await api.actions.resetItemDateTimes();
if (result.error) {
notify.error("Failed to reset date and time values.");
return;
}
notify.success(`${result.data.completed} assets have been updated.`);
}
</script>
<style scoped></style>

View file

@ -9,7 +9,7 @@ import (
func dateTypes(names []string) map[*regexp.Regexp]string { func dateTypes(names []string) map[*regexp.Regexp]string {
result := make(map[*regexp.Regexp]string) result := make(map[*regexp.Regexp]string)
for _, name := range names { for _, name := range names {
result[regexp.MustCompile(fmt.Sprintf(`%s: string`, name))] = fmt.Sprintf(`%s: Date`, name) result[regexp.MustCompile(fmt.Sprintf(`%s: string`, name))] = fmt.Sprintf(`%s: Date | string`, name)
} }
return result return result
} }