diff --git a/backend/internal/data/repo/repo_items.go b/backend/internal/data/repo/repo_items.go index 53f21a4..72ba904 100644 --- a/backend/internal/data/repo/repo_items.go +++ b/backend/internal/data/repo/repo_items.go @@ -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,15 +822,36 @@ func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID) updateQ := e.db.Item.Update().Where(item.ID(i.ID)) if !i.PurchaseTime.IsZero() { - updateQ.SetPurchaseTime(toDateOnly(i.PurchaseTime)) + switch { + case i.PurchaseTime.Year() < 100: + updateQ.ClearPurchaseTime() + default: + updateQ.SetPurchaseTime(toDateOnly(i.PurchaseTime)) + } + } else { + updateQ.ClearPurchaseTime() } if !i.SoldTime.IsZero() { - updateQ.SetSoldTime(toDateOnly(i.SoldTime)) + switch { + case i.SoldTime.Year() < 100: + updateQ.ClearSoldTime() + default: + updateQ.SetSoldTime(toDateOnly(i.SoldTime)) + } + } else { + updateQ.ClearSoldTime() } if !i.WarrantyExpires.IsZero() { - updateQ.SetWarrantyExpires(toDateOnly(i.WarrantyExpires)) + switch { + case i.WarrantyExpires.Year() < 100: + updateQ.ClearWarrantyExpires() + default: + updateQ.SetWarrantyExpires(toDateOnly(i.WarrantyExpires)) + } + } else { + updateQ.ClearWarrantyExpires() } _, err = updateQ.Save(ctx) @@ -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 } diff --git a/frontend/components/Form/DatePicker.vue b/frontend/components/Form/DatePicker.vue index 98b34f3..2a681ea 100644 --- a/frontend/components/Form/DatePicker.vue +++ b/frontend/components/Form/DatePicker.vue @@ -9,11 +9,15 @@ - + diff --git a/frontend/components/global/DateTime.vue b/frontend/components/global/DateTime.vue index 0388a67..e5a76a9 100644 --- a/frontend/components/global/DateTime.vue +++ b/frontend/components/global/DateTime.vue @@ -22,6 +22,6 @@ return ""; } - return fmtDate(props.date, props.format, props.datetimeType); + return fmtDate(props.date, props.format); }); diff --git a/frontend/composables/use-formatters.ts b/frontend/composables/use-formatters.ts index 035d27f..32a2a73 100644 --- a/frontend/composables/use-formatters.ts +++ b/frontend/composables/use-formatters.ts @@ -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; diff --git a/frontend/composables/use-theme.ts b/frontend/composables/use-theme.ts index cbd26d3..89e3d39 100644 --- a/frontend/composables/use-theme.ts +++ b/frontend/composables/use-theme.ts @@ -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); + }); +} diff --git a/frontend/lib/datelib/datelib.test.ts b/frontend/lib/datelib/datelib.test.ts new file mode 100644 index 0000000..171d2cc --- /dev/null +++ b/frontend/lib/datelib/datelib.test.ts @@ -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); + }); +}); diff --git a/frontend/lib/datelib/datelib.ts b/frontend/lib/datelib/datelib.ts new file mode 100644 index 0000000..c70dbf9 --- /dev/null +++ b/frontend/lib/datelib/datelib.ts @@ -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])); +} diff --git a/frontend/package.json b/frontend/package.json index e63b826..8eb8a95 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,10 +41,12 @@ "@tailwindcss/aspect-ratio": "^0.4.0", "@tailwindcss/forms": "^0.5.2", "@tailwindcss/typography": "^0.5.4", + "@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", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index dc10375..1958dc6 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -26,6 +26,9 @@ dependencies: '@tailwindcss/typography': specifier: ^0.5.4 version: 0.5.8(tailwindcss@3.2.4) + '@vuepic/vue-datepicker': + specifier: ^8.1.1 + version: 8.1.1(vue@3.3.4) '@vueuse/nuxt': specifier: ^10.0.0 version: 10.0.0(nuxt@3.6.5)(rollup@2.79.1)(vue@3.3.4) @@ -38,6 +41,9 @@ dependencies: daisyui: specifier: ^2.24.0 version: 2.43.0(autoprefixer@10.4.13)(postcss@8.4.19) + date-fns: + specifier: ^3.3.1 + version: 3.3.1 dompurify: specifier: ^3.0.0 version: 3.0.0 @@ -114,7 +120,7 @@ devDependencies: version: 5.0.2 vite-plugin-eslint: specifier: ^1.8.1 - version: 1.8.1(eslint@8.29.0)(vite@4.3.9) + version: 1.8.1(eslint@8.29.0)(vite@5.0.5) vitest: specifier: ^1.0.0 version: 1.0.0(@types/node@18.11.12) @@ -3160,7 +3166,7 @@ packages: '@nuxt/kit': 3.6.5(rollup@2.79.1) pathe: 1.1.1 ufo: 1.3.2 - vite-plugin-pwa: 0.16.7(vite@4.3.9)(workbox-build@7.0.0)(workbox-window@7.0.0) + vite-plugin-pwa: 0.16.7(vite@5.0.5)(workbox-build@7.0.0)(workbox-window@7.0.0) dev: true /@vitejs/plugin-vue-jsx@3.0.1(vite@4.3.9)(vue@3.3.4): @@ -3343,6 +3349,16 @@ packages: /@vue/shared@3.3.4: resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==} + /@vuepic/vue-datepicker@8.1.1(vue@3.3.4): + resolution: {integrity: sha512-/t9+dROb/hYN/DInff8ctIiVm5tVyuJdiWA+nWcjHOLjUhvAQ9vyJwhxAkoWX5o0EB5x5XeCME2cajfTPz86RA==} + engines: {node: '>=18.12.0'} + peerDependencies: + vue: '>=3.2.0' + dependencies: + date-fns: 3.3.1 + vue: 3.3.4 + dev: false + /@vueuse/core@10.0.0(vue@3.3.4): resolution: {integrity: sha512-Q/p2xaGVFVrJ0E4ID1SM35WIa0Eo4AeKPSLKuLpYG09zgUWuwEaY4lBoNzLzkdLjzq5goIJ3DfYFI0wt8W4MkA==} dependencies: @@ -4066,7 +4082,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} @@ -4456,6 +4472,10 @@ packages: resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} engines: {node: '>= 12'} + /date-fns@3.3.1: + resolution: {integrity: sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==} + dev: false + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -5532,19 +5552,11 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - optional: true - /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: true optional: true /function-bind@1.1.1: @@ -7021,7 +7033,7 @@ packages: engines: {node: ^14.18.0 || >=16.10.0} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 /nuxt@3.6.5(@types/node@18.11.12)(eslint@8.29.0)(rollup@2.79.1)(typescript@5.0.2): resolution: {integrity: sha512-0A7V8B1HrIXX9IlqPc2w+5ZPXi+7MYa9QVhtuGYuLvjRKoSFANhCoMPRP6pKdoxigM1MBxhLue2VmHA/VbtJCw==} @@ -8223,14 +8235,14 @@ packages: engines: {node: '>=10.0.0'} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 /rollup@3.26.2: resolution: {integrity: sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 /rollup@4.6.1: resolution: {integrity: sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==} @@ -9361,7 +9373,7 @@ packages: vscode-languageserver-textdocument: 1.0.8 vscode-uri: 3.0.6 - /vite-plugin-eslint@1.8.1(eslint@8.29.0)(vite@4.3.9): + /vite-plugin-eslint@1.8.1(eslint@8.29.0)(vite@5.0.5): resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==} peerDependencies: eslint: '>=7' @@ -9371,10 +9383,10 @@ packages: '@types/eslint': 8.4.10 eslint: 8.29.0 rollup: 2.79.1 - vite: 4.3.9(@types/node@18.11.12) + vite: 5.0.5(@types/node@18.11.12) dev: true - /vite-plugin-pwa@0.16.7(vite@4.3.9)(workbox-build@7.0.0)(workbox-window@7.0.0): + /vite-plugin-pwa@0.16.7(vite@5.0.5)(workbox-build@7.0.0)(workbox-window@7.0.0): resolution: {integrity: sha512-4WMA5unuKlHs+koNoykeuCfTcqEGbiTRr8sVYUQMhc6tWxZpSRnv9Ojk4LKmqVhoPGHfBVCdGaMo8t9Qidkc1Q==} engines: {node: '>=16.0.0'} peerDependencies: @@ -9385,7 +9397,7 @@ packages: debug: 4.3.4 fast-glob: 3.3.2 pretty-bytes: 6.1.1 - vite: 4.3.9(@types/node@18.11.12) + vite: 5.0.5(@types/node@18.11.12) workbox-build: 7.0.0 workbox-window: 7.0.0 transitivePeerDependencies: @@ -9422,7 +9434,7 @@ packages: postcss: 8.4.24 rollup: 3.26.2 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 /vite@5.0.5(@types/node@18.11.12): resolution: {integrity: sha512-OekeWqR9Ls56f3zd4CaxzbbS11gqYkEiBtnWFFgYR2WV8oPJRRKq0mpskYy/XaoCL3L7VINDhqqOMNDiYdGvGg==}