From fd14f8a6c1d3366e9152a6c64ac94987b7e64a03 Mon Sep 17 00:00:00 2001 From: Rynare Date: Sat, 26 Apr 2025 00:26:10 +0700 Subject: [PATCH] update tampilan landing page, add new modal, update table, new library for dependecies, update style tailwind, add type safe for ts, update nuxt config. --- app.vue | 1 + assets/css/main.tw.css | 37 ++++++ .../landing/demo/modalMakePrediction.vue | 67 ++++++++++ components/landing/header.vue | 2 +- components/landing/introduction/hero-sct.vue | 6 +- components/my/date-picker.vue | 78 ++++++++++++ components/my/form/input-with-type.vue | 50 ++++++++ components/my/input-currency.vue | 3 + components/my/input-number.vue | 25 ++++ components/my/modal/update-prediction-row.vue | 4 + composables/usePredictionTable.ts | 38 ++++++ composables/useSpreadsheet.ts | 45 +++++++ nuxt.config.ts | 9 +- package-lock.json | 114 ++++++++++++++++++ package.json | 4 + pages/demo.vue | 88 +++++++++++++- .../landing-page/demo/modalMakePrediction.ts | 13 ++ utils/spreadsheet/sheetsToJSON.ts | 13 ++ 18 files changed, 588 insertions(+), 9 deletions(-) create mode 100644 assets/css/main.tw.css create mode 100644 components/landing/demo/modalMakePrediction.vue create mode 100644 components/my/date-picker.vue create mode 100644 components/my/form/input-with-type.vue create mode 100644 components/my/input-currency.vue create mode 100644 components/my/input-number.vue create mode 100644 components/my/modal/update-prediction-row.vue create mode 100644 composables/usePredictionTable.ts create mode 100644 composables/useSpreadsheet.ts create mode 100644 types/landing-page/demo/modalMakePrediction.ts create mode 100644 utils/spreadsheet/sheetsToJSON.ts diff --git a/app.vue b/app.vue index 8f62b8b..9343435 100644 --- a/app.vue +++ b/app.vue @@ -1,3 +1,4 @@ diff --git a/assets/css/main.tw.css b/assets/css/main.tw.css new file mode 100644 index 0000000..40c6c9e --- /dev/null +++ b/assets/css/main.tw.css @@ -0,0 +1,37 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; +/* assets/css/main.css */ +.nuxtui-btn { + @apply + focus:outline-none + disabled:cursor-not-allowed + disabled:opacity-75 + aria-disabled:cursor-not-allowed + aria-disabled:opacity-75 + flex-shrink-0 + font-medium + rounded-md + text-sm + gap-x-1.5 + px-2.5 + py-1.5 + shadow-sm + text-white + dark:text-gray-900 + bg-blue-500 + hover:bg-blue-600 + disabled:bg-blue-500 + aria-disabled:bg-blue-500 + dark:bg-blue-400 + dark:hover:bg-blue-500 + dark:disabled:bg-blue-400 + dark:aria-disabled:bg-blue-400 + focus-visible:outline + focus-visible:outline-2 + focus-visible:outline-offset-2 + focus-visible:outline-blue-500 + dark:focus-visible:outline-blue-400 + inline-flex + items-center; +} diff --git a/components/landing/demo/modalMakePrediction.vue b/components/landing/demo/modalMakePrediction.vue new file mode 100644 index 0000000..909f82c --- /dev/null +++ b/components/landing/demo/modalMakePrediction.vue @@ -0,0 +1,67 @@ + + \ No newline at end of file diff --git a/components/landing/header.vue b/components/landing/header.vue index ae3a1c7..c440504 100644 --- a/components/landing/header.vue +++ b/components/landing/header.vue @@ -75,7 +75,7 @@
diff --git a/components/landing/introduction/hero-sct.vue b/components/landing/introduction/hero-sct.vue index a91dbe3..95974a2 100644 --- a/components/landing/introduction/hero-sct.vue +++ b/components/landing/introduction/hero-sct.vue @@ -1,6 +1,6 @@ \ No newline at end of file diff --git a/components/my/form/input-with-type.vue b/components/my/form/input-with-type.vue new file mode 100644 index 0000000..dc647dc --- /dev/null +++ b/components/my/form/input-with-type.vue @@ -0,0 +1,50 @@ + + \ No newline at end of file diff --git a/components/my/input-currency.vue b/components/my/input-currency.vue new file mode 100644 index 0000000..8b3c051 --- /dev/null +++ b/components/my/input-currency.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/components/my/input-number.vue b/components/my/input-number.vue new file mode 100644 index 0000000..f15a7fe --- /dev/null +++ b/components/my/input-number.vue @@ -0,0 +1,25 @@ + + + diff --git a/components/my/modal/update-prediction-row.vue b/components/my/modal/update-prediction-row.vue new file mode 100644 index 0000000..1f35e87 --- /dev/null +++ b/components/my/modal/update-prediction-row.vue @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/composables/usePredictionTable.ts b/composables/usePredictionTable.ts new file mode 100644 index 0000000..8b24a7a --- /dev/null +++ b/composables/usePredictionTable.ts @@ -0,0 +1,38 @@ +import type { TableColumn } from '#ui/types' +export function usePredictionTableColumn() { + const requiredColumn = ['date', 'product code', 'product name', 'sold(qty)'] + const columnKeys = ref([]) + const columns: ComputedRef = computed(() => { + if (columnKeys.value.length >= 1) { + return [{ + key: 'actions', + sortable: true, + label: 'Actions' + }, ...columnKeys.value.map(v => ({ + key: v, + sortable: true, + label: v + }) as TableColumn)] + } else { + return [{ + key: 'actions', + sortable: true, + label: 'Actions' + }, ...requiredColumn.map(v => ({ + key: v, + sortable: true, + label: v, + }))] + } + }) + const missingColumn = computed(() => { + const currentColumn = columns.value.map(v => v.key) + return requiredColumn.filter(v => !currentColumn.includes(v)) + }) + + return { + columnKeys, + columns, + missingColumn + } +} \ No newline at end of file diff --git a/composables/useSpreadsheet.ts b/composables/useSpreadsheet.ts new file mode 100644 index 0000000..5a43b49 --- /dev/null +++ b/composables/useSpreadsheet.ts @@ -0,0 +1,45 @@ +import { sheetToJSON } from "~/utils/spreadsheet/sheetsToJSON" + +export function useFileToJSON() { + const toast = useToast() + const file = ref(null) + const status = ref<'idle' | 'loading' | 'error' | 'success'>('idle') + const result = ref[]>([]) + const error = ref(null) + watch(file, async (newVal) => { + if (!newVal) + return + status.value = 'loading' + error.value = null + try { + const json = await sheetToJSON(newVal); + if (json) { + result.value = json as Record[] + } + } catch (e: unknown) { + status.value = 'error' + if (e instanceof Error) { + error.value = e + } + toast.add({ + title: 'Error', + icon: 'i-heroicons-x-circle', + color: 'red', + description: error.value?.message + }) + } finally { + if (status.value !== 'error') { + status.value = 'success' + toast.add({ + title: 'Success', + icon: 'i-heroicons-document-check', + color: 'green', + description: 'File Imported Successfully.' + }) + } + } + }) + return { + file, status, result, error + } +} \ No newline at end of file diff --git a/nuxt.config.ts b/nuxt.config.ts index a2a0147..70ceaaa 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -12,7 +12,7 @@ export default defineNuxtConfig({ }, compatibilityDate: '2024-11-01', devtools: { enabled: true }, - modules: ['@nuxt/image', '@nuxt/ui', 'shadcn-nuxt'], + modules: ['@nuxt/image', '@nuxt/ui', 'shadcn-nuxt', 'dayjs-nuxt'], ui: { prefix: 'NuxtUi' }, @@ -30,5 +30,8 @@ export default defineNuxtConfig({ image: { format: ['webp'], quality: 80, - } -}) \ No newline at end of file + }, + css: [ + '@/assets/css/main.tw.css' + ] +}) diff --git a/package-lock.json b/package-lock.json index fc28c3f..028b9b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,14 +12,18 @@ "@vueuse/core": "^13.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^2.30.0", + "dayjs-nuxt": "^2.1.11", "lucide-vue-next": "^0.485.0", "nuxt": "^3.16.1", "reka-ui": "^2.2.0", "shadcn-nuxt": "^1.0.3", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", + "v-calendar": "^3.1.2", "vue": "^3.5.13", "vue-router": "^4.5.0", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "zod": "^3.24.2" } }, @@ -422,6 +426,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", @@ -2953,12 +2969,24 @@ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", + "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", + "license": "MIT" + }, "node_modules/@types/parse-path": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", "license": "MIT" }, + "node_modules/@types/resize-observer-browser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.11.tgz", + "integrity": "sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ==", + "license": "MIT" + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -4826,6 +4854,47 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/date-fns-tz": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.1.tgz", + "integrity": "sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==", + "license": "MIT", + "peerDependencies": { + "date-fns": "2.x" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/dayjs-nuxt": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/dayjs-nuxt/-/dayjs-nuxt-2.1.11.tgz", + "integrity": "sha512-KDDNiET7KAKf6yzL3RaPWq5aV7ql9QTt5fIDYv+4eOegDmnEQGjwkKYADDystsKtPjt7QZerpVbhC96o3BIyqQ==", + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.7.4", + "dayjs": "^1.11.10" + } + }, "node_modules/db0": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/db0/-/db0-0.3.1.tgz", @@ -8904,6 +8973,12 @@ "node": ">=4" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/reka-ui": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.2.0.tgz", @@ -10852,6 +10927,24 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/v-calendar": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/v-calendar/-/v-calendar-3.1.2.tgz", + "integrity": "sha512-QDWrnp4PWCpzUblctgo4T558PrHgHzDtQnTeUNzKxfNf29FkCeFpwGd9bKjAqktaa2aJLcyRl45T5ln1ku34kg==", + "license": "MIT", + "dependencies": { + "@types/lodash": "^4.14.165", + "@types/resize-observer-browser": "^0.1.7", + "date-fns": "^2.16.1", + "date-fns-tz": "^2.0.0", + "lodash": "^4.17.20", + "vue-screen-utils": "^1.0.0-beta.13" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "vue": "^3.2.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -11221,6 +11314,15 @@ "vue": "^3.2.0" } }, + "node_modules/vue-screen-utils": { + "version": "1.0.0-beta.13", + "resolved": "https://registry.npmjs.org/vue-screen-utils/-/vue-screen-utils-1.0.0-beta.13.tgz", + "integrity": "sha512-EJ/8TANKhFj+LefDuOvZykwMr3rrLFPLNb++lNBqPOpVigT2ActRg6icH9RFQVm4nHwlHIHSGm5OY/Clar9yIg==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -11376,6 +11478,18 @@ } } }, + "node_modules/xlsx": { + "version": "0.20.3", + "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==", + "license": "Apache-2.0", + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/xss": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", diff --git a/package.json b/package.json index af5f9eb..a7e0b0c 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,18 @@ "@vueuse/core": "^13.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^2.30.0", + "dayjs-nuxt": "^2.1.11", "lucide-vue-next": "^0.485.0", "nuxt": "^3.16.1", "reka-ui": "^2.2.0", "shadcn-nuxt": "^1.0.3", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", + "v-calendar": "^3.1.2", "vue": "^3.5.13", "vue-router": "^4.5.0", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "zod": "^3.24.2" } } diff --git a/pages/demo.vue b/pages/demo.vue index 4a91088..4bbf4e4 100644 --- a/pages/demo.vue +++ b/pages/demo.vue @@ -1,7 +1,91 @@ + diff --git a/types/landing-page/demo/modalMakePrediction.ts b/types/landing-page/demo/modalMakePrediction.ts new file mode 100644 index 0000000..6710c44 --- /dev/null +++ b/types/landing-page/demo/modalMakePrediction.ts @@ -0,0 +1,13 @@ +export type TDurationType = 'daily' | 'weekly' | 'monthly' +export type TModalMakePredictionModel = { + recordPeriod?: TDurationType, + selectedProduct?: string, + predictionPeriod?: TDurationType, +} +export type TProduct = { + product_code: string, + product_name: string +} +export type TModalMakePredictionProps = { + products?: TProduct[] +} \ No newline at end of file diff --git a/utils/spreadsheet/sheetsToJSON.ts b/utils/spreadsheet/sheetsToJSON.ts new file mode 100644 index 0000000..fa658f2 --- /dev/null +++ b/utils/spreadsheet/sheetsToJSON.ts @@ -0,0 +1,13 @@ +import * as XLSX from 'xlsx'; + +export async function sheetToJSON(file: File) { + try { + const fileBuffer = await file.arrayBuffer(); + const workbook = XLSX.read(fileBuffer); + const sheet = workbook.Sheets[workbook.SheetNames[0]] + const json = XLSX.utils.sheet_to_json(sheet) + return json + } catch (error: unknown) { + throw error + } +} \ No newline at end of file