mirror of
https://github.com/hay-kot/homebox.git
synced 2025-07-07 02:58:35 +00:00
fix/feat: primary photo auto set and auto-set primary photo (#651)
* fix error when item doesn't have photo attachment
* automatically detect image types and set primary photo if first
* remove charts.js from libs
Former-commit-id: 321a83b634
This commit is contained in:
parent
bd1a241be1
commit
81e76d9dd4
13 changed files with 41 additions and 424 deletions
|
@ -1,94 +0,0 @@
|
|||
<template>
|
||||
<DoughnutChart
|
||||
:chart-options="chartOptions"
|
||||
:chart-data="chartData"
|
||||
:chart-id="chartId"
|
||||
:dataset-id-key="datasetIdKey"
|
||||
:css-classes="cssClasses"
|
||||
:styles="styles"
|
||||
:width="width"
|
||||
:height="height"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Doughnut as DoughnutChart } from "vue-chartjs";
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, CategoryScale, LinearScale, ArcElement } from "chart.js";
|
||||
import { TChartData } from "vue-chartjs/dist/types";
|
||||
|
||||
ChartJS.register(Title, Tooltip, Legend, CategoryScale, LinearScale, ArcElement);
|
||||
|
||||
export default defineComponent({
|
||||
name: "BarChart",
|
||||
components: {
|
||||
DoughnutChart,
|
||||
},
|
||||
props: {
|
||||
chartId: {
|
||||
type: String,
|
||||
default: "bar-chart",
|
||||
},
|
||||
datasetIdKey: {
|
||||
type: String,
|
||||
default: "label",
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 400,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 400,
|
||||
},
|
||||
cssClasses: {
|
||||
default: "",
|
||||
type: String,
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
chartData: {
|
||||
type: Object as () => TChartData<"doughnut", number[], unknown>,
|
||||
default: () => {
|
||||
return {
|
||||
labels: ["Red", "Blue", "Yellow"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First Dataset",
|
||||
data: [300, 50, 100],
|
||||
backgroundColor: ["rgb(255, 99, 132)", "rgb(54, 162, 235)", "rgb(255, 205, 86)"],
|
||||
hoverOffset: 4,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartOptions: {
|
||||
responsive: false,
|
||||
// Legend on the left
|
||||
plugins: {
|
||||
legend: {
|
||||
position: "bottom",
|
||||
},
|
||||
// Display percentage
|
||||
// tooltip: {
|
||||
// callbacks: {
|
||||
// label: context => {
|
||||
// const label = context.dataset?.label || "";
|
||||
// const value = context.parsed.y;
|
||||
// return `${label}: ${value}%`;
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,113 +0,0 @@
|
|||
<template>
|
||||
<div ref="el" class="min-h-full flex flex-col">
|
||||
{{ styles }}
|
||||
<LineChart :chart-options="options" :chart-data="chartData" :styles="styles" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Line as LineChart } from "vue-chartjs";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
LineElement,
|
||||
} from "chart.js";
|
||||
import { TChartData } from "vue-chartjs/dist/types";
|
||||
|
||||
ChartJS.register(Title, Tooltip, Legend, CategoryScale, LinearScale, PointElement, LineElement);
|
||||
|
||||
export default defineComponent({
|
||||
name: "BarChart",
|
||||
components: {
|
||||
LineChart,
|
||||
},
|
||||
props: {
|
||||
chartId: {
|
||||
type: String,
|
||||
default: "bar-chart",
|
||||
},
|
||||
datasetIdKey: {
|
||||
type: String,
|
||||
default: "label",
|
||||
},
|
||||
cssClasses: {
|
||||
default: "",
|
||||
type: String,
|
||||
},
|
||||
chartData: {
|
||||
type: Object as () => TChartData<"line", number[], unknown>,
|
||||
default: () => {
|
||||
return {
|
||||
labels: ["January", "February", "March"],
|
||||
datasets: [{ data: [40, 20, 12] }],
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const el = ref<HTMLElement | null>(null);
|
||||
|
||||
const calcHeight = ref(0);
|
||||
const calcWidth = ref(0);
|
||||
|
||||
function resize() {
|
||||
calcHeight.value = el.value?.offsetHeight || 0;
|
||||
calcWidth.value = el.value?.offsetWidth || 0;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
resize();
|
||||
window.addEventListener("resize", resize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("resize", resize);
|
||||
});
|
||||
|
||||
const styles = computed(() => {
|
||||
return {
|
||||
height: `${calcHeight.value}px`,
|
||||
width: `${calcWidth.value}px`,
|
||||
position: "relative",
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
el,
|
||||
parentHeight: calcHeight,
|
||||
styles,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
display: false,
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
borderWidth: 5,
|
||||
},
|
||||
point: {
|
||||
radius: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -28,10 +28,12 @@ export type ItemsQuery = {
|
|||
};
|
||||
|
||||
export class AttachmentsAPI extends BaseAPI {
|
||||
add(id: string, file: File | Blob, filename: string, type: AttachmentTypes) {
|
||||
add(id: string, file: File | Blob, filename: string, type: AttachmentTypes | null = null) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("type", type);
|
||||
if (type) {
|
||||
formData.append("type", type);
|
||||
}
|
||||
formData.append("name", filename);
|
||||
|
||||
return this.http.post<FormData, ItemOut>({
|
||||
|
|
|
@ -44,7 +44,6 @@
|
|||
"@vueuse/nuxt": "^10.0.0",
|
||||
"@vueuse/router": "^10.0.0",
|
||||
"autoprefixer": "^10.4.8",
|
||||
"chart.js": "^4.0.1",
|
||||
"daisyui": "^2.24.0",
|
||||
"dompurify": "^3.0.0",
|
||||
"h3": "^1.7.1",
|
||||
|
@ -54,7 +53,6 @@
|
|||
"postcss": "^8.4.16",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"vue": "^3.3.1",
|
||||
"vue-chartjs": "^4.1.2",
|
||||
"vue-router": "4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
import { TChartData } from "vue-chartjs/dist/types";
|
||||
import { UserClient } from "~~/lib/api/user";
|
||||
|
||||
export function purchasePriceOverTimeChart(api: UserClient) {
|
||||
const { data: timeseries } = useAsyncData(async () => {
|
||||
const { data } = await api.stats.totalPriceOverTime();
|
||||
return data;
|
||||
});
|
||||
|
||||
const primary = useCssVar("--p");
|
||||
|
||||
return computed(() => {
|
||||
if (!timeseries.value) {
|
||||
return {
|
||||
labels: ["Purchase Price"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Purchase Price",
|
||||
data: [],
|
||||
backgroundColor: primary.value,
|
||||
borderColor: primary.value,
|
||||
},
|
||||
],
|
||||
} as TChartData<"line", number[], unknown>;
|
||||
}
|
||||
|
||||
let start = timeseries.value?.valueAtStart;
|
||||
|
||||
return {
|
||||
labels: timeseries?.value.entries.map(t => new Date(t.date).toDateString()) || [],
|
||||
datasets: [
|
||||
{
|
||||
label: "Purchase Price",
|
||||
data:
|
||||
timeseries.value?.entries.map(t => {
|
||||
start += t.value;
|
||||
return start;
|
||||
}) || [],
|
||||
backgroundColor: primary.value,
|
||||
borderColor: primary.value,
|
||||
},
|
||||
],
|
||||
} as TChartData<"line", number[], unknown>;
|
||||
});
|
||||
}
|
||||
|
||||
export function inventoryByLocationChart(api: UserClient) {
|
||||
const { data: donutSeries } = useAsyncData(async () => {
|
||||
const { data } = await api.stats.locations();
|
||||
return data;
|
||||
});
|
||||
|
||||
const primary = useCssVar("--p");
|
||||
const secondary = useCssVar("--s");
|
||||
const neutral = useCssVar("--n");
|
||||
|
||||
return computed(() => {
|
||||
return {
|
||||
labels: donutSeries.value?.map(l => l.name) || [],
|
||||
datasets: [
|
||||
{
|
||||
label: "Value",
|
||||
data: donutSeries.value?.map(l => l.total) || [],
|
||||
backgroundColor: [primary.value, secondary.value, neutral.value],
|
||||
borderColor: [primary.value, secondary.value, neutral.value],
|
||||
hoverOffset: 4,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
|
@ -22,55 +22,11 @@
|
|||
|
||||
const itemTable = itemsTable(api);
|
||||
const stats = statCardData(api);
|
||||
|
||||
// const purchasePriceOverTime = purchasePriceOverTimeChart(api);
|
||||
|
||||
// const inventoryByLocation = inventoryByLocationChart(api);
|
||||
|
||||
// const refDonutEl = ref<HTMLDivElement>();
|
||||
|
||||
// const donutElWidth = computed(() => {
|
||||
// return refDonutEl.value?.clientWidth || 0;
|
||||
// });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<BaseContainer class="flex flex-col gap-12 pb-16">
|
||||
<!-- <section class="grid grid-cols-6 gap-6">
|
||||
<article class="col-span-4">
|
||||
<Subtitle> Inventory Value Over Time </Subtitle>
|
||||
<BaseCard>
|
||||
<div class="p-10 h-[300px]">
|
||||
<ClientOnly>
|
||||
<ChartLine :chart-data="purchasePriceOverTime" />
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</BaseCard>
|
||||
</article>
|
||||
<article class="col-span-2">
|
||||
<Subtitle>
|
||||
Inventory By
|
||||
<span class="btn-group">
|
||||
<button class="btn btn-xs btn-active text-no-transform">Locations</button>
|
||||
<button class="btn btn-xs text-no-transform">Labels</button>
|
||||
</span>
|
||||
</Subtitle>
|
||||
<BaseCard class="h-[300px]">
|
||||
<div ref="refDonutEl" class="grid place-content-center h-full">
|
||||
<ClientOnly>
|
||||
<ChartDonut
|
||||
chart-id="donut"
|
||||
:width="donutElWidth - 50"
|
||||
:height="265"
|
||||
:chart-data="inventoryByLocation"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</BaseCard>
|
||||
</article>
|
||||
</section> -->
|
||||
|
||||
<section>
|
||||
<Subtitle> Quick Statistics </Subtitle>
|
||||
<div class="grid grid-cols-2 gap-2 md:grid-cols-4 md:gap-6">
|
||||
|
|
|
@ -253,7 +253,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
uploadAttachment([first], AttachmentTypes.Attachment);
|
||||
uploadAttachment([first], null);
|
||||
}
|
||||
|
||||
const dropPhoto = (files: File[] | null) => uploadAttachment(files, AttachmentTypes.Photo);
|
||||
|
@ -262,7 +262,7 @@
|
|||
const dropManual = (files: File[] | null) => uploadAttachment(files, AttachmentTypes.Manual);
|
||||
const dropReceipt = (files: File[] | null) => uploadAttachment(files, AttachmentTypes.Receipt);
|
||||
|
||||
async function uploadAttachment(files: File[] | null, type: AttachmentTypes) {
|
||||
async function uploadAttachment(files: File[] | null, type: AttachmentTypes | null) {
|
||||
if (!files || files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
|
21
frontend/pnpm-lock.yaml
generated
21
frontend/pnpm-lock.yaml
generated
|
@ -35,9 +35,6 @@ dependencies:
|
|||
autoprefixer:
|
||||
specifier: ^10.4.8
|
||||
version: 10.4.13(postcss@8.4.19)
|
||||
chart.js:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
daisyui:
|
||||
specifier: ^2.24.0
|
||||
version: 2.43.0(autoprefixer@10.4.13)(postcss@8.4.19)
|
||||
|
@ -65,9 +62,6 @@ dependencies:
|
|||
vue:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.4
|
||||
vue-chartjs:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2(chart.js@4.0.1)(vue@3.3.4)
|
||||
vue-router:
|
||||
specifier: '4'
|
||||
version: 4.1.6(vue@3.3.4)
|
||||
|
@ -3770,11 +3764,6 @@ packages:
|
|||
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
|
||||
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||
|
||||
/chart.js@4.0.1:
|
||||
resolution: {integrity: sha512-5/8/9eBivwBZK81mKvmIwTb2Pmw4D/5h1RK9fBWZLLZ8mCJ+kfYNmV9rMrGoa5Hgy2/wVDBMLSUDudul2/9ihA==}
|
||||
engines: {pnpm: ^7.0.0}
|
||||
dev: false
|
||||
|
||||
/check-error@1.0.2:
|
||||
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
|
||||
dev: true
|
||||
|
@ -9107,16 +9096,6 @@ packages:
|
|||
dependencies:
|
||||
ufo: 1.1.2
|
||||
|
||||
/vue-chartjs@4.1.2(chart.js@4.0.1)(vue@3.3.4):
|
||||
resolution: {integrity: sha512-QSggYjeFv/L4jFSBQpX8NzrAvX0B+Ha6nDgxkTG8tEXxYOOTwKI4phRLe+B4f+REnkmg7hgPY24R0cixZJyXBg==}
|
||||
peerDependencies:
|
||||
chart.js: ^3.7.0
|
||||
vue: ^3.0.0-0 || ^2.6.0
|
||||
dependencies:
|
||||
chart.js: 4.0.1
|
||||
vue: 3.3.4
|
||||
dev: false
|
||||
|
||||
/vue-demi@0.13.11(vue@3.3.4):
|
||||
resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue