Added calender
This commit is contained in:
194
package-lock.json
generated
194
package-lock.json
generated
@@ -8,11 +8,15 @@
|
||||
"name": "eamco_frontend_v2",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@fullcalendar/daygrid": "^6.1.19",
|
||||
"@fullcalendar/interaction": "^6.1.19",
|
||||
"@fullcalendar/vue3": "^6.1.19",
|
||||
"@kyvg/vue3-notification": "^3.1.0",
|
||||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"axios": "^1.6.2",
|
||||
"axios": "^1.11.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"html-to-image": "^1.11.11",
|
||||
"html2canvas": "^1.4.1",
|
||||
"moment": "^2.30.1",
|
||||
@@ -469,6 +473,40 @@
|
||||
"resolved": "https://registry.npmjs.org/@foliojs-fork/restructure/-/restructure-2.0.2.tgz",
|
||||
"integrity": "sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA=="
|
||||
},
|
||||
"node_modules/@fullcalendar/core": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.19.tgz",
|
||||
"integrity": "sha512-z0aVlO5e4Wah6p6mouM0UEqtRf1MZZPt4mwzEyU6kusaNL+dlWQgAasF2cK23hwT4cmxkEmr4inULXgpyeExdQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"preact": "~10.12.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/daygrid": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.19.tgz",
|
||||
"integrity": "sha512-IAAfnMICnVWPjpT4zi87i3FEw0xxSza0avqY/HedKEz+l5MTBYvCDPOWDATpzXoLut3aACsjktIyw9thvIcRYQ==",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/interaction": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.19.tgz",
|
||||
"integrity": "sha512-GOciy79xe8JMVp+1evAU3ytdwN/7tv35t5i1vFkifiuWcQMLC/JnLg/RA2s4sYmQwoYhTw/p4GLcP0gO5B3X5w==",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/vue3": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/vue3/-/vue3-6.1.19.tgz",
|
||||
"integrity": "sha512-j5eUSxx0xIy3ADljo0f5B9PhjqXnCQ+7nUMPfsslc2eGVjp4F74YvY3dyd6OBbg13IvpsjowkjncGipYMQWmTA==",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.19",
|
||||
"vue": "^3.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
@@ -1204,12 +1242,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.8",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
|
||||
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -1328,6 +1366,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-css": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
@@ -1527,6 +1577,11 @@
|
||||
"url": "https://opencollective.com/daisyui"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
|
||||
},
|
||||
"node_modules/de-indent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||
@@ -1609,6 +1664,19 @@
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
@@ -1639,12 +1707,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -1657,6 +1722,31 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
@@ -1800,12 +1890,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1856,15 +1948,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -1873,6 +1970,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.3.10",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
|
||||
@@ -1908,11 +2017,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -1929,21 +2038,10 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -2200,6 +2298,14 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/merge": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz",
|
||||
@@ -2671,6 +2777,16 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.12.1",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
|
||||
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
|
||||
@@ -9,11 +9,15 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fullcalendar/daygrid": "^6.1.19",
|
||||
"@fullcalendar/interaction": "^6.1.19",
|
||||
"@fullcalendar/vue3": "^6.1.19",
|
||||
"@kyvg/vue3-notification": "^3.1.0",
|
||||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"axios": "^1.6.2",
|
||||
"axios": "^1.11.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"html-to-image": "^1.11.11",
|
||||
"html2canvas": "^1.4.1",
|
||||
"moment": "^2.30.1",
|
||||
|
||||
@@ -18,13 +18,12 @@
|
||||
</li>
|
||||
|
||||
<div class="font-bold text-lg text-gray-500 pt-5 ">Customer</div>
|
||||
|
||||
<li class="text-white">
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
<div class=" hover:underline py-1">All Customers</div>
|
||||
</router-link>
|
||||
|
||||
</li>
|
||||
|
||||
<div class="font-bold text-lg text-gray-500 pt-5">Delivery</div>
|
||||
<li class="text-white">
|
||||
<router-link :to="{ name: 'delivery' }">
|
||||
@@ -57,7 +56,6 @@
|
||||
</div>
|
||||
<div class=" hover:underline py-1" v-else>Waiting Deliveries </div>
|
||||
</router-link>
|
||||
|
||||
<router-link :to="{ name: 'deliveryIssue' }">
|
||||
<div class=" hover:underline py-1">Issue Tickets</div>
|
||||
</router-link>
|
||||
@@ -70,16 +68,17 @@
|
||||
</div>
|
||||
<div class=" hover:underline py-1" v-else>Pending Payment </div>
|
||||
</router-link>
|
||||
|
||||
|
||||
<router-link :to="{ name: 'deliveryFinalized' }">
|
||||
<div class=" hover:underline py-1">Finalized Tickets</div>
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="font-bold text-lg text-gray-500 pt-5">Service</div>
|
||||
<li class="text-white">
|
||||
<router-link :to="{ name: 'ServiceHome' }">
|
||||
<div class=" hover:underline py-1">Service Home</div>
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
|
||||
<div class="font-bold text-lg text-gray-500 pt-5">Automatics</div>
|
||||
@@ -95,13 +94,13 @@
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
|
||||
<div class="font-bold text-lg text-gray-500 pt-5">Employees</div>
|
||||
<li class="text-white">
|
||||
<router-link :to="{ name: 'employee' }">
|
||||
<div class=" hover:underline py-1">Employees</div>
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
<div class="font-bold text-lg text-gray-500 pt-5">Admin</div>
|
||||
<li class="text-white">
|
||||
<router-link :to="{ name: 'oilprice' }">
|
||||
|
||||
@@ -68,18 +68,19 @@
|
||||
|
||||
<td class="flex gap-5 ">
|
||||
<router-link :to="{ name: 'deliveryCreate', params: { id: person['id'] } }"
|
||||
class="btn-sm btn bg-orange-600 text-white">
|
||||
Create Delivery
|
||||
class="btn-sm btn bg-orange-600 text-white">
|
||||
Create Delivery
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'CalenderCustomer', params: { id: person['id'] } }"
|
||||
class="btn-sm btn bg-orange-600 text-white">
|
||||
Create Service Call
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'customerEdit', params: { id: person['id'] } }" class="btn-sm btn btn-secondary">
|
||||
Edit Customer
|
||||
Edit Customer
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'cardadd', params: { id: person['id'] } }">
|
||||
<button class="btn btn-sm btn-secondary text-white ">Add CreditCard</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: person['id'] } }"
|
||||
class="btn btn-secondary btn-sm">
|
||||
View Profile
|
||||
class="btn btn-secondary btn-sm">
|
||||
View Profile
|
||||
</router-link>
|
||||
|
||||
</td>
|
||||
|
||||
@@ -47,6 +47,12 @@
|
||||
Create Delivery
|
||||
</router-link>
|
||||
|
||||
|
||||
<router-link :to="{ name: 'CalenderCustomer', params: { id: customer.id } }"
|
||||
class="btn-sm btn bg-orange-600 text-white">
|
||||
Create Service Call
|
||||
</router-link>
|
||||
|
||||
<router-link :to="{ name: 'customerEdit', params: { id: customer.id } }"
|
||||
class="btn-sm btn btn-secondary">
|
||||
Edit Customer
|
||||
|
||||
@@ -147,9 +147,7 @@
|
||||
<div v-else>
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 py-5">
|
||||
No Cards on File!
|
||||
<a @click.prevent="test()" class="cursor-pointer underline hover:text-blue-300">
|
||||
Edit Card
|
||||
</a>
|
||||
|
||||
<!-- <router-link :to="{ name: 'cardadd', params: { id: customer.id } }">
|
||||
<button class="btn btn-sm bg-blue-700 text-white">Add Credit Card</button>
|
||||
</router-link> -->
|
||||
@@ -506,9 +504,7 @@ export default defineComponent({
|
||||
});
|
||||
});
|
||||
},
|
||||
test() {
|
||||
this.CreateOilOrderForm.basicInfo.gallons_ordered = '100'
|
||||
},
|
||||
|
||||
getCustomerDelivery(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/customer/' + userid + '/1';
|
||||
axios({
|
||||
|
||||
@@ -665,7 +665,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok === 'true') {
|
||||
if (response.data.ok === true) {
|
||||
this.updatestatus()
|
||||
this.$router.push({ name: "deliveryOrder", params: { id: this.deliveryOrder.id } });
|
||||
}
|
||||
|
||||
157
src/pages/service/ServiceEditModal.vue
Normal file
157
src/pages/service/ServiceEditModal.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<!-- Modal Overlay -->
|
||||
<div class="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center z-50">
|
||||
<!-- Modal Content -->
|
||||
<div class="relative bg-white p-6 rounded-lg shadow-xl w-full max-w-lg">
|
||||
|
||||
<!-- Modal Header -->
|
||||
<div class="flex justify-between items-center border-b pb-3 mb-4">
|
||||
<h3 class="text-2xl font-bold text-gray-800">Edit Service Call</h3>
|
||||
<span class="font-bold text-white px-3 py-1 mr-10 rounded" :style="{ backgroundColor: getServiceTypeColor(editableService.type_service_call) }">
|
||||
{{ getServiceTypeName(editableService.type_service_call) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="$emit('close-modal')"
|
||||
type="button"
|
||||
class="absolute top-0 right-0 mt-4 mr-4 text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
aria-label="Close modal"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Form for Editing -->
|
||||
<form @submit.prevent="saveChanges">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Scheduled Date -->
|
||||
<div class="mb-4">
|
||||
<label for="edit-date" class="block text-sm font-medium text-gray-700">Scheduled Date</label>
|
||||
<input type="date" id="edit-date" v-model="editableService.scheduled_date" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
|
||||
</div>
|
||||
|
||||
<!-- Service Type -->
|
||||
<div class="mb-4">
|
||||
<label for="edit-service-type" class="block text-sm font-medium text-gray-700">Type of Service</label>
|
||||
<select id="edit-service-type" v-model.number="editableService.type_service_call" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
|
||||
<option v-for="option in serviceOptions" :key="option.value" :value="option.value">
|
||||
{{ option.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="mb-4">
|
||||
<label for="edit-description" class="block text-sm font-medium text-gray-700">Description</label>
|
||||
<!-- ====================================================== -->
|
||||
<!-- ============== THIS LINE HAS BEEN UPDATED ============== -->
|
||||
<!-- ====================================================== -->
|
||||
<textarea
|
||||
id="edit-description"
|
||||
v-model="editableService.description"
|
||||
rows="4"
|
||||
required
|
||||
class="mt-1 block w-full rounded-md border border-gray-300 shadow-sm text-black focus:border-indigo-500 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
||||
></textarea>
|
||||
<!-- ====================================================== -->
|
||||
<!-- ================ END OF UPDATED LINE ================ -->
|
||||
<!-- ====================================================== -->
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="mt-6 flex justify-between items-center">
|
||||
<button @click.prevent="confirmDelete" type="button" class="px-4 py-2 bg-red-600 text-white font-medium rounded-md shadow-sm hover:bg-red-700">
|
||||
Delete Call
|
||||
</button>
|
||||
<div class="flex space-x-3">
|
||||
<button @click.prevent="$emit('close-modal')" type="button" class="px-4 py-2 bg-gray-200 text-gray-800 font-medium rounded-md shadow-sm hover:bg-gray-300">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-blue-600 text-white font-medium rounded-md shadow-sm hover:bg-blue-700">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
|
||||
interface ServiceCall {
|
||||
id: number;
|
||||
scheduled_date: string;
|
||||
customer_name: string;
|
||||
customer_address: string;
|
||||
customer_town: string;
|
||||
type_service_call: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServiceEditModal',
|
||||
props: {
|
||||
service: {
|
||||
type: Object as PropType<ServiceCall>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editableService: {} as Partial<ServiceCall>,
|
||||
serviceOptions: [
|
||||
{ text: 'Tune-up', value: 0 },
|
||||
{ text: 'No Heat', value: 1 },
|
||||
{ text: 'Fix', value: 2 },
|
||||
{ text: 'Tank Install', value: 3 },
|
||||
{ text: 'Other', value: 4 },
|
||||
],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
service: {
|
||||
handler(newVal) {
|
||||
this.editableService = JSON.parse(JSON.stringify(newVal));
|
||||
if (this.editableService.scheduled_date) {
|
||||
this.editableService.scheduled_date = this.editableService.scheduled_date.split('T')[0];
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
saveChanges() {
|
||||
this.$emit('save-changes', this.editableService);
|
||||
},
|
||||
confirmDelete() {
|
||||
if (window.confirm(`Are you sure you want to delete this service call for "${this.service.customer_name}"?`)) {
|
||||
this.$emit('delete-service', this.service.id);
|
||||
}
|
||||
},
|
||||
getServiceTypeName(typeId: number | undefined | null): string {
|
||||
if (typeId === undefined || typeId === null) {
|
||||
return 'Unknown';
|
||||
}
|
||||
const typeMap: { [key: number]: string } = {
|
||||
0: 'Tune-up', 1: 'No Heat', 2: 'Fix', 3: 'Tank Install', 4: 'Other',
|
||||
};
|
||||
return typeMap[typeId] || 'Unknown';
|
||||
},
|
||||
getServiceTypeColor(typeId: number | undefined | null): string {
|
||||
if (typeId === undefined || typeId === null) {
|
||||
return 'gray';
|
||||
}
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: 'blue', 1: 'red', 2: 'green', 3: '#B58900', 4: 'black',
|
||||
};
|
||||
return colorMap[typeId] || 'gray';
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
205
src/pages/service/ServiceHome.vue
Normal file
205
src/pages/service/ServiceHome.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Service Calls</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex text-2xl mb-5 font-bold">
|
||||
Upcoming Service Calls
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="text-center p-10">
|
||||
<p>Loading upcoming service calls...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="services.length === 0" class="text-center p-10 bg-gray-100 rounded-md">
|
||||
<p>No upcoming service calls found.</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class=" bg-neutral">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Scheduled Date</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer Name</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Address</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Service Type</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class=" divide-y bg-neutral">
|
||||
<tr v-for="service in services" :key="service.id" @click="openEditModal(service)" class="hover:bg-blue-600 hover:text-black cursor-pointer">
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ formatDate(service.scheduled_date) }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ service.customer_name }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ service.customer_address }}, {{ service.customer_town }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap font-medium" :style="{ color: getServiceTypeColor(service.type_service_call) }">
|
||||
{{ getServiceTypeName(service.type_service_call) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-normal text-sm text-gray-500">{{ service.description }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
<ServiceEditModal
|
||||
v-if="selectedServiceForEdit"
|
||||
:service="selectedServiceForEdit"
|
||||
@close-modal="closeEditModal"
|
||||
@save-changes="handleSaveChanges"
|
||||
@delete-service="handleDeleteService"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import axios from 'axios';
|
||||
import authHeader from '../../services/auth.header';
|
||||
import Header from '../../layouts/headers/headerauth.vue';
|
||||
import SideBar from '../../layouts/sidebar/sidebar.vue';
|
||||
import Footer from '../../layouts/footers/footer.vue';
|
||||
import ServiceEditModal from './ServiceEditModal.vue';
|
||||
|
||||
interface ServiceCall {
|
||||
id: number;
|
||||
scheduled_date: string;
|
||||
customer_name: string;
|
||||
customer_address: string;
|
||||
customer_town: string;
|
||||
type_service_call: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServiceHome',
|
||||
components: { Header, SideBar, Footer, ServiceEditModal },
|
||||
data() {
|
||||
return {
|
||||
user: null,
|
||||
services: [] as ServiceCall[],
|
||||
isLoading: true,
|
||||
selectedServiceForEdit: null as ServiceCall | null,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.userStatus();
|
||||
this.fetchUpcomingServices();
|
||||
},
|
||||
methods: {
|
||||
async fetchUpcomingServices(): Promise<void> {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/upcoming';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
this.services = response.data;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch upcoming service calls:", error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
|
||||
// --- HELPER METHODS WITH IMPLEMENTATIONS RESTORED ---
|
||||
|
||||
formatDate(dateString: string): string {
|
||||
const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' };
|
||||
// Adding a timeZone option helps prevent off-by-one-day errors
|
||||
return new Date(dateString).toLocaleDateString(undefined, { ...options, timeZone: 'UTC' });
|
||||
},
|
||||
|
||||
getServiceTypeName(typeId: number): string {
|
||||
const typeMap: { [key: number]: string } = {
|
||||
0: 'Tune-up',
|
||||
1: 'No Heat',
|
||||
2: 'Fix',
|
||||
3: 'Tank Install',
|
||||
4: 'Other',
|
||||
};
|
||||
return typeMap[typeId] || 'Unknown Service';
|
||||
},
|
||||
|
||||
getServiceTypeColor(typeId: number): string {
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: 'blue',
|
||||
1: 'red',
|
||||
2: 'green',
|
||||
3: '#B58900', // A darker yellow for text
|
||||
4: 'black',
|
||||
};
|
||||
return colorMap[typeId] || 'gray';
|
||||
},
|
||||
|
||||
// --- MODAL MANAGEMENT METHODS ---
|
||||
|
||||
openEditModal(service: ServiceCall) {
|
||||
this.selectedServiceForEdit = service;
|
||||
},
|
||||
closeEditModal() {
|
||||
this.selectedServiceForEdit = null;
|
||||
},
|
||||
async handleSaveChanges(updatedService: ServiceCall) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
|
||||
if (response.data.ok) {
|
||||
const index = this.services.findIndex(s => s.id === updatedService.id);
|
||||
if (index !== -1) {
|
||||
this.services[index] = response.data.service;
|
||||
}
|
||||
this.closeEditModal();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
},
|
||||
async handleDeleteService(serviceId: number) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
|
||||
if (response.data.ok) {
|
||||
this.services = this.services.filter(s => s.id !== serviceId);
|
||||
this.closeEditModal();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete service call:", error);
|
||||
alert("An error occurred while deleting. Please check the console.");
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
155
src/pages/service/calender/CalendarCustomer.vue
Normal file
155
src/pages/service/calender/CalendarCustomer.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="w-full px-10">
|
||||
<div class="text-sm breadcrumbs mb-4">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
|
||||
<li v-if="customer">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex h-screen font-sans">
|
||||
<div v-if="isLoading" class="w-1/4 p-4 border-r">
|
||||
<h2 class="text-xl font-bold">Loading Customer...</h2>
|
||||
</div>
|
||||
<EventSidebar v-else-if="customer" :customer="customer" @event-scheduled="handleEventScheduled" />
|
||||
<div v-else class="w-1/4 p-4 border-r">
|
||||
<h2 class="text-xl font-bold text-red-500">Error</h2>
|
||||
<p>Could not load customer data. You can still view the master calendar.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-4 overflow-auto">
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions" />
|
||||
</div>
|
||||
<EventModal v-if="selectedEvent" :event="selectedEvent" @close-modal="selectedEvent = null" @delete-event="handleEventDelete" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import Header from '../../../layouts/headers/headerauth.vue';
|
||||
import FullCalendar from '@fullcalendar/vue3';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import { CalendarOptions, EventApi, EventClickArg } from '@fullcalendar/core';
|
||||
import EventSidebar from './EventSidebar.vue';
|
||||
import EventModal from './EventModal.vue';
|
||||
import axios from 'axios';
|
||||
import authHeader from '../../../services/auth.header'; // Assuming you have this service
|
||||
|
||||
// --- Interfaces (no changes) ---
|
||||
interface Customer { id: number; customer_last_name: string; customer_first_name: string; customer_town: string; customer_state: number; customer_zip: string; customer_phone_number: string; customer_address: string; customer_home_type: number; customer_apt: string; }
|
||||
interface EventExtendedProps { description: string; type_service_call: number; }
|
||||
interface AppEvent { id?: string; title: string; start: string; end?: string; extendedProps: EventExtendedProps; }
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarCustomer',
|
||||
components: { Header, FullCalendar, EventSidebar, EventModal },
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
selectedEvent: null as EventApi | null,
|
||||
calendarOptions: {} as CalendarOptions,
|
||||
customer: null as Customer | null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'$route.params.id': {
|
||||
handler(newId) {
|
||||
if (newId) this.getCustomer(newId as string);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.calendarOptions = {
|
||||
plugins: [dayGridPlugin, interactionPlugin],
|
||||
initialView: 'dayGridMonth',
|
||||
weekends: true,
|
||||
events: [],
|
||||
eventClick: this.handleEventClick,
|
||||
};
|
||||
// --- KEY CHANGE: Fetch ALL events when the component is created ---
|
||||
this.fetchEvents();
|
||||
},
|
||||
methods: {
|
||||
async getCustomer(customerId: string): Promise<void> {
|
||||
this.isLoading = true;
|
||||
this.customer = null;
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`;
|
||||
const response = await axios.get(path, { withCredentials: true });
|
||||
if (response.data && response.data.id) {
|
||||
this.customer = response.data;
|
||||
// --- REMOVED: No longer need to fetch events from here ---
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("API call to get customer FAILED:", error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// --- KEY CHANGE: This method now fetches ALL events and is independent ---
|
||||
async fetchEvents(): Promise<void> {
|
||||
try {
|
||||
console.log("fetchEvents: Fetching ALL events for the master calendar.");
|
||||
// Call the new '/service/all' endpoint
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/all`;
|
||||
const response = await axios.get(path, { headers: authHeader(), withCredentials: true });
|
||||
|
||||
console.log("fetchEvents: Received all events from API:", response.data);
|
||||
this.calendarOptions.events = response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching all calendar events:", error);
|
||||
}
|
||||
},
|
||||
|
||||
handleEventClick(clickInfo: EventClickArg): void {
|
||||
this.selectedEvent = clickInfo.event;
|
||||
},
|
||||
|
||||
async handleEventScheduled(eventData: any): Promise<void> {
|
||||
if (!this.customer) {
|
||||
alert("Error: A customer must be loaded in the sidebar to create a new event.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload = {
|
||||
expected_delivery_date: eventData.start, type_service_call: eventData.type_service_call,
|
||||
customer_id: this.customer.id, description: eventData.extendedProps.description,
|
||||
};
|
||||
const path = import.meta.env.VITE_BASE_URL + "/service/create";
|
||||
const response = await axios.post(path, payload, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
if (response.data.ok === true) {
|
||||
// After creating a new event, refresh the entire master calendar
|
||||
await this.fetchEvents();
|
||||
} else {
|
||||
console.error("Failed to create event:", response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating event:", error);
|
||||
}
|
||||
},
|
||||
|
||||
async handleEventDelete(eventId: string): Promise<void> {
|
||||
// ... (no changes needed in this method)
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${eventId}`;
|
||||
const response = await axios.delete(path, { withCredentials: true, headers: authHeader() });
|
||||
if (response.data.ok === true) {
|
||||
const calendarApi = (this.$refs.fullCalendar as any).getApi();
|
||||
const eventToRemove = calendarApi.getEventById(eventId);
|
||||
if (eventToRemove) eventToRemove.remove();
|
||||
this.selectedEvent = null;
|
||||
}
|
||||
} catch (error) { console.error("Error deleting event:", error); }
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
59
src/pages/service/calender/EventModal.vue
Normal file
59
src/pages/service/calender/EventModal.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center z-50">
|
||||
<div class="relative bg-white p-6 rounded-lg shadow-xl w-full max-w-md">
|
||||
<div class="text-left">
|
||||
<h3 class="text-2xl leading-6 font-bold text-gray-900 mb-2">{{ event?.title }}</h3>
|
||||
<div class="mt-4">
|
||||
<p class="text-sm text-gray-600 whitespace-pre-wrap">{{ event?.extendedProps.description }}</p>
|
||||
<p class="text-sm text-gray-500 mt-4">
|
||||
<strong>Start:</strong> {{ formatEventDate(event?.start) }}
|
||||
</p>
|
||||
<p v-if="event?.end" class="text-sm text-gray-500">
|
||||
<strong>End:</strong> {{ formatEventDate(event?.end, true) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end space-x-3">
|
||||
<button @click="confirmDelete" class="px-4 py-2 bg-red-600 text-white text-base font-medium rounded-md shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||
Delete
|
||||
</button>
|
||||
<button @click="$emit('close-modal')" class="px-4 py-2 bg-gray-200 text-gray-800 text-base font-medium rounded-md shadow-sm hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-400">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { EventApi } from '@fullcalendar/core';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EventModal',
|
||||
props: {
|
||||
event: {
|
||||
type: Object as PropType<EventApi | null>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
confirmDelete() {
|
||||
if (this.event && window.confirm(`Are you sure you want to delete "${this.event.title}"?`)) {
|
||||
this.$emit('delete-event', this.event.id);
|
||||
}
|
||||
},
|
||||
formatEventDate(date: Date | string | null | undefined, isEndDate: boolean = false): string {
|
||||
if (!date) return 'N/A';
|
||||
|
||||
let dateObj = dayjs(date);
|
||||
if (isEndDate) {
|
||||
dateObj = dateObj.subtract(1, 'day');
|
||||
}
|
||||
|
||||
return dateObj.format('MMMM D, YYYY');
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
164
src/pages/service/calender/EventSidebar.vue
Normal file
164
src/pages/service/calender/EventSidebar.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="w-1/4 p-4 border-r">
|
||||
<h2 class="text-xl font-bold mb-4">Schedule Service</h2>
|
||||
|
||||
<form @submit.prevent="submitEvent">
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="event-label" class="block text-sm font-medium text-gray-200">Calendar Label</label>
|
||||
<input type="text" id="event-label" v-model="event.title" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="service_type" class="block text-sm font-medium text-gray-200">Type of Service</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs bg-white text-black" id="service_type" v-model="selectedService" required>
|
||||
<option disabled value="">Please select one</option>
|
||||
<option v-for="option in serviceOptions" :key="option.value" :value="option.value">
|
||||
{{ option.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="event-description" class="block text-sm font-medium text-gray-200">Description</label>
|
||||
<textarea id="event-description" v-model="event.description" rows="3" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="event-date" class="block text-sm font-medium text-gray-200">Day / Month</label>
|
||||
<input type="date" id="event-date" v-model="event.date" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="event-time" class="block text-sm font-medium text-gray-200">Time (Hour)</label>
|
||||
<select id="event-time" v-model="event.time" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
|
||||
<option v-for="hour in 24" :key="hour" :value="hour - 1">{{ (hour - 1).toString().padStart(2, '0') }}:00</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="event-end-date" class="block text-sm font-medium text-gray-200">End Date (Optional for multi-day)</label>
|
||||
<input type="date" id="event-end-date" v-model="event.endDate" :min="event.date" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700">
|
||||
Add Event
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div v-if="customer" class="mt-10 border-t pt-4">
|
||||
<div class="font-bold text-lg">
|
||||
{{ customer.customer_first_name }} {{ customer.customer_last_name }}
|
||||
</div>
|
||||
<div>{{ customer.customer_address }}</div>
|
||||
<div v-if="customer.customer_apt">{{ customer.customer_apt }}</div>
|
||||
<div>
|
||||
<span>{{ customer.customer_town }},</span>
|
||||
<span class="pl-1">{{ customerStateName }}</span>
|
||||
<span class="pl-1">{{ customer.customer_zip }}</span>
|
||||
</div>
|
||||
<div>{{ customer.customer_phone_number }}</div>
|
||||
<div class="text-sm text-gray-500 mt-2">{{ customerHomeType }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
interface Customer {
|
||||
id: number;
|
||||
customer_last_name: string;
|
||||
customer_first_name: string;
|
||||
customer_town: string;
|
||||
customer_state: number;
|
||||
customer_zip: string;
|
||||
customer_phone_number: string;
|
||||
customer_address: string;
|
||||
customer_home_type: number;
|
||||
customer_apt: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EventSidebar',
|
||||
props: {
|
||||
customer: {
|
||||
type: Object as PropType<Customer | null>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedService: '' as string | number,
|
||||
serviceOptions: [
|
||||
{ text: 'Tune-up', value: 0 },
|
||||
{ text: 'No Heat', value: 1 },
|
||||
{ text: 'Fix', value: 2 },
|
||||
{ text: 'Tank Install', value: 3 },
|
||||
{ text: 'Other', value: 4 },
|
||||
],
|
||||
event: {
|
||||
title: '',
|
||||
description: '',
|
||||
date: dayjs().format('YYYY-MM-DD'),
|
||||
endDate: '',
|
||||
time: 12,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
customerStateName(): string {
|
||||
if (!this.customer) return '';
|
||||
const stateMap: { [key: number]: string } = {
|
||||
0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire',
|
||||
3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York',
|
||||
};
|
||||
return stateMap[this.customer.customer_state] || 'Unknown';
|
||||
},
|
||||
customerHomeType(): string {
|
||||
if (!this.customer) return '';
|
||||
const homeTypeMap: { [key: number]: string } = {
|
||||
0: 'Residential', 1: 'Apartment', 2: 'Condo', 3: 'Commercial',
|
||||
4: 'Business', 5: 'Construction', 6: 'Container',
|
||||
};
|
||||
return homeTypeMap[this.customer.customer_home_type] || 'Unknown';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submitEvent() {
|
||||
if (!this.customer) {
|
||||
alert("Cannot submit: No customer data is loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
const startDateTime = dayjs(`${this.event.date} ${this.event.time}:00`).format('YYYY-MM-DDTHH:mm:ss');
|
||||
const endDateTime = this.event.endDate ? dayjs(this.event.endDate).add(1, 'day').format('YYYY-MM-DD') : undefined;
|
||||
|
||||
const eventPayload = {
|
||||
title: this.event.title,
|
||||
start: startDateTime,
|
||||
type_service_call: this.selectedService,
|
||||
end: endDateTime,
|
||||
extendedProps: {
|
||||
description: this.event.description,
|
||||
},
|
||||
};
|
||||
|
||||
this.$emit('event-scheduled', eventPayload);
|
||||
|
||||
this.event.title = '';
|
||||
this.selectedService = '';
|
||||
this.event.description = '';
|
||||
this.event.endDate = '';
|
||||
this.event.date = dayjs().format('YYYY-MM-DD');
|
||||
this.event.time = 12;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
24
src/pages/service/calender/api.js
Normal file
24
src/pages/service/calender/api.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL;
|
||||
|
||||
function authHeader() {
|
||||
// Return authorization header
|
||||
return {};
|
||||
}
|
||||
|
||||
export function createEvent(payload) {
|
||||
const path = `${BASE_URL}/service/create`; // Example endpoint
|
||||
return axios.post(path, payload, {
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteEventById(eventId) {
|
||||
const path = `${BASE_URL}/service/delete/${eventId}`; // Example endpoint
|
||||
return axios.delete(path, {
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
});
|
||||
}
|
||||
24
src/pages/service/routes.ts
Normal file
24
src/pages/service/routes.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
|
||||
|
||||
import ServiceHome from './ServiceHome.vue' // Adjust the import path
|
||||
import CalendarCustomer from './calender/CalendarCustomer.vue'
|
||||
|
||||
const serviceRoutes = [
|
||||
{
|
||||
path: '/service',
|
||||
name: 'ServiceHome',
|
||||
component: ServiceHome
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: '/service/calender/:id',
|
||||
name: 'CalenderCustomer',
|
||||
component: CalendarCustomer,
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
export default serviceRoutes
|
||||
//sourceMappingURL=index.ts.map
|
||||
@@ -12,7 +12,7 @@ import Error404 from '../pages/error/Error404.vue'
|
||||
import adminRoutes from "../pages/admin/routes.ts";
|
||||
import tickerRoutes from "../pages/ticket/routes.ts";
|
||||
import moneyRoutes from "../pages/money/routes.ts";
|
||||
|
||||
import serviceRoutes from "../pages/service/routes.ts";
|
||||
const routes = [
|
||||
...moneyRoutes,
|
||||
...cardRoutes,
|
||||
@@ -24,6 +24,7 @@ const routes = [
|
||||
...autoRoutes,
|
||||
...adminRoutes,
|
||||
...tickerRoutes,
|
||||
...serviceRoutes,
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
|
||||
3
src/types/fullcalendar-interaction.d.ts
vendored
Normal file
3
src/types/fullcalendar-interaction.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare module '@fullcalendar/interaction';
|
||||
declare module '@fullcalendar/daygrid';
|
||||
declare module '@fullcalendar/vue3';
|
||||
5
src/vite-env.d.ts
vendored
5
src/vite-env.d.ts
vendored
@@ -1 +1,6 @@
|
||||
/// <reference types="vite/client" />
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
Reference in New Issue
Block a user