mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
commit
a5b4fa2670
127 changed files with 87092 additions and 351 deletions
|
|
@ -2,6 +2,14 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
|
|||
|
||||
## Getting Started
|
||||
|
||||
When first getting set up you'll firstly want to install the existing dependencies. To do this, simply run
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# or
|
||||
yarn install
|
||||
```
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
463
package-lock.json
generated
463
package-lock.json
generated
|
|
@ -21,8 +21,10 @@
|
|||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@remixicon/react": "^4.2.0",
|
||||
"@tanstack/react-query": "^4.29.12",
|
||||
"@tanstack/react-table": "^8.9.3",
|
||||
"@tremor/react": "^3.16.0",
|
||||
"@types/node": "20.2.3",
|
||||
"@types/react": "18.2.7",
|
||||
"@types/react-dom": "18.2.4",
|
||||
|
|
@ -42,11 +44,12 @@
|
|||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"tailwind-merge": "^1.13.2",
|
||||
"tailwindcss": "3.3.2",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tailwindcss-animate": "^1.0.6",
|
||||
"typescript": "5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@testing-library/cypress": "^9.0.0",
|
||||
"@types/pg": "^8.10.2",
|
||||
"cypress": "^12.17.1",
|
||||
|
|
@ -1143,10 +1146,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@headlessui/react": {
|
||||
"version": "1.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.14.tgz",
|
||||
"integrity": "sha512-znzdq9PG8rkwcu9oQ2FwIy0ZFtP9Z7ycS+BAqJ3R5EIqC/0bJGvhT7193rFf+45i9nnPsYvCQVW4V/bB9Xc+gA==",
|
||||
"version": "1.7.19",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.19.tgz",
|
||||
"integrity": "sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==",
|
||||
"dependencies": {
|
||||
"@tanstack/react-virtual": "^3.0.0-beta.60",
|
||||
"client-only": "^0.0.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -1157,6 +1161,17 @@
|
|||
"react-dom": "^16 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/@headlessui/tailwindcss": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/tailwindcss/-/tailwindcss-0.2.0.tgz",
|
||||
"integrity": "sha512-fpL830Fln1SykOCboExsWr3JIVeQKieLJ3XytLe/tt1A0XzqUthOftDmjcCYLW62w7mQI7wXcoPXr3tZ9QfGxw==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": "^3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroicons/react": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.0.18.tgz",
|
||||
|
|
@ -2412,6 +2427,14 @@
|
|||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@remixicon/react": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@remixicon/react/-/react-4.2.0.tgz",
|
||||
"integrity": "sha512-eGhKpZ88OU0qkcY9pJu6khBmItDV82nU130E6C68yc+FbljueHlUYy/4CrJsmf860RIDMay2Rpzl27OSJ81miw==",
|
||||
"peerDependencies": {
|
||||
"react": ">=18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/eslint-patch": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.0.tgz",
|
||||
|
|
@ -2446,6 +2469,18 @@
|
|||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/forms": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
|
||||
"integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mini-svg-data-uri": "^1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "4.29.11",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.11.tgz",
|
||||
|
|
@ -2500,6 +2535,22 @@
|
|||
"react-dom": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-virtual": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.3.0.tgz",
|
||||
"integrity": "sha512-QFxmTSZBniq15S0vSZ55P4ToXquMXwJypPXyX/ux7sYo6a2FX3/zWoRLLc4eIOGWTjvzqcIVNKhcuFb+OZL3aQ==",
|
||||
"dependencies": {
|
||||
"@tanstack/virtual-core": "3.3.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/table-core": {
|
||||
"version": "8.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.9.3.tgz",
|
||||
|
|
@ -2512,6 +2563,15 @@
|
|||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/virtual-core": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.3.0.tgz",
|
||||
"integrity": "sha512-A0004OAa1FcUkPHeeGoKgBrAgjH+uHdDPrw1L7RpkwnODYqRvoilqsHPs8cyTjMg1byZBbiNpQAq2TlFLIaQag==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/cypress": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-9.0.0.tgz",
|
||||
|
|
@ -2580,12 +2640,85 @@
|
|||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tremor/react": {
|
||||
"version": "3.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@tremor/react/-/react-3.16.0.tgz",
|
||||
"integrity": "sha512-wKOGTMFwBbadYolFoOKYPjvwtgHZVB/1tS0QLaNpOpoBn3Tlx/Bb2RNsLPNGCmFTb9tNfOSGoOBwd8uukWA9xg==",
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.19.2",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"react-day-picker": "^8.9.1",
|
||||
"react-transition-state": "^2.1.1",
|
||||
"recharts": "^2.10.3",
|
||||
"tailwind-merge": "^1.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
|
||||
"integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
||||
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
|
||||
},
|
||||
"node_modules/@types/d3-ease": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||
"dependencies": {
|
||||
"@types/d3-color": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz",
|
||||
"integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ=="
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
|
||||
"integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
|
||||
"dependencies": {
|
||||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "3.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz",
|
||||
"integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
|
||||
"dependencies": {
|
||||
"@types/d3-path": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-time": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz",
|
||||
"integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw=="
|
||||
},
|
||||
"node_modules/@types/d3-timer": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
|
|
@ -3526,9 +3659,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001489",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz",
|
||||
"integrity": "sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==",
|
||||
"version": "1.0.30001641",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001641.tgz",
|
||||
"integrity": "sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
|
@ -4046,6 +4179,116 @@
|
|||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"dependencies": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-shape": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||
"dependencies": {
|
||||
"d3-path": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||
"dependencies": {
|
||||
"d3-array": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time-format": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||
"dependencies": {
|
||||
"d3-time": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-timer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
|
|
@ -4063,6 +4306,21 @@
|
|||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.11"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/date-fns"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.9",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz",
|
||||
|
|
@ -4085,6 +4343,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
|
||||
},
|
||||
"node_modules/deep-equal": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz",
|
||||
|
|
@ -4240,6 +4503,15 @@
|
|||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
|
||||
|
|
@ -5179,6 +5451,11 @@
|
|||
"integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
|
|
@ -5291,10 +5568,18 @@
|
|||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
|
||||
"integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.12",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
|
||||
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
"@nodelib/fs.walk": "^1.2.3",
|
||||
|
|
@ -5962,6 +6247,14 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
|
|
@ -6409,9 +6702,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
|
||||
"integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
|
||||
"version": "1.21.0",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
|
||||
"integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
|
|
@ -6647,8 +6940,7 @@
|
|||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
|
|
@ -6871,6 +7163,15 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mini-svg-data-uri": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mini-svg-data-uri": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
|
|
@ -7982,6 +8283,19 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-day-picker": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.0.tgz",
|
||||
"integrity": "sha512-mz+qeyrOM7++1NCb1ARXmkjMkzWVh2GL9YiPbRjKe0zHccvekk4HE+0MPOZOrosn8r8zTHIIeOUXTmXRqmkRmg==",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/gpbl"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"date-fns": "^2.28.0 || ^3.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
|
|
@ -8044,6 +8358,20 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-smooth": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz",
|
||||
"integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==",
|
||||
"dependencies": {
|
||||
"fast-equals": "^5.0.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-transition-group": "^4.4.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-style-singleton": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
||||
|
|
@ -8066,6 +8394,30 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-state": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-state/-/react-transition-state-2.1.1.tgz",
|
||||
"integrity": "sha512-kQx5g1FVu9knoz1T1WkapjUgFz08qQ/g1OmuWGi3/AoEFfS0kStxrPlZx81urjCXdz2d+1DqLpU6TyLW/Ro04Q==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
|
@ -8112,6 +8464,44 @@
|
|||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "2.12.5",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.5.tgz",
|
||||
"integrity": "sha512-Cy+BkqrFIYTHJCyKHJEPvbHE2kVQEP6PKbOHJ8ztRGTAhvHuUnCwDaKVb13OwRFZ0QNUk1QvGTDdgWSMbuMtKw==",
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0",
|
||||
"eventemitter3": "^4.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"react-is": "^16.10.2",
|
||||
"react-smooth": "^4.0.0",
|
||||
"recharts-scale": "^0.4.4",
|
||||
"tiny-invariant": "^1.3.1",
|
||||
"victory-vendor": "^36.6.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts-scale": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
|
||||
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
|
||||
"dependencies": {
|
||||
"decimal.js-light": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts/node_modules/clsx": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
|
||||
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
|
|
@ -8944,28 +9334,28 @@
|
|||
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.13.2.tgz",
|
||||
"integrity": "sha512-R2/nULkdg1VR/EL4RXg4dEohdoxNUJGLMnWIQnPKL+O9Twu7Cn3Rxi4dlXkDzZrEGtR+G+psSXFouWlpTyLhCQ==",
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
||||
"integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
|
||||
"integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
|
||||
"integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
"chokidar": "^3.5.3",
|
||||
"didyoumean": "^1.2.2",
|
||||
"dlv": "^1.1.3",
|
||||
"fast-glob": "^3.2.12",
|
||||
"fast-glob": "^3.3.0",
|
||||
"glob-parent": "^6.0.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"jiti": "^1.18.2",
|
||||
"jiti": "^1.21.0",
|
||||
"lilconfig": "^2.1.0",
|
||||
"micromatch": "^4.0.5",
|
||||
"normalize-path": "^3.0.0",
|
||||
|
|
@ -8977,7 +9367,6 @@
|
|||
"postcss-load-config": "^4.0.1",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"postcss-selector-parser": "^6.0.11",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"resolve": "^1.22.2",
|
||||
"sucrase": "^3.32.0"
|
||||
},
|
||||
|
|
@ -9060,6 +9449,11 @@
|
|||
"next-tick": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
|
||||
},
|
||||
"node_modules/titleize": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
|
||||
|
|
@ -9394,6 +9788,27 @@
|
|||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/victory-vendor": {
|
||||
"version": "36.9.2",
|
||||
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
|
||||
"integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
|
||||
"dependencies": {
|
||||
"@types/d3-array": "^3.0.3",
|
||||
"@types/d3-ease": "^3.0.0",
|
||||
"@types/d3-interpolate": "^3.0.1",
|
||||
"@types/d3-scale": "^4.0.2",
|
||||
"@types/d3-shape": "^3.1.0",
|
||||
"@types/d3-time": "^3.0.0",
|
||||
"@types/d3-timer": "^3.0.0",
|
||||
"d3-array": "^3.1.6",
|
||||
"d3-ease": "^3.0.1",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-shape": "^3.1.0",
|
||||
"d3-time": "^3.0.0",
|
||||
"d3-timer": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/wait-on": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -27,8 +27,10 @@
|
|||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@remixicon/react": "^4.2.0",
|
||||
"@tanstack/react-query": "^4.29.12",
|
||||
"@tanstack/react-table": "^8.9.3",
|
||||
"@tremor/react": "^3.16.0",
|
||||
"@types/node": "20.2.3",
|
||||
"@types/react": "18.2.7",
|
||||
"@types/react-dom": "18.2.4",
|
||||
|
|
@ -48,11 +50,12 @@
|
|||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"tailwind-merge": "^1.13.2",
|
||||
"tailwindcss": "3.3.2",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tailwindcss-animate": "^1.0.6",
|
||||
"typescript": "5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@testing-library/cypress": "^9.0.0",
|
||||
"@types/pg": "^8.10.2",
|
||||
"cypress": "^12.17.1",
|
||||
|
|
|
|||
7
public/.well-known/microsoft-identity-association.json
Normal file
7
public/.well-known/microsoft-identity-association.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"associatedApplications": [
|
||||
{
|
||||
"applicationId": "069e75ee-ba54-45ff-ba77-a06f29c0e21c"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
public/pfp_solar_image.png
Normal file
BIN
public/pfp_solar_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 952 KiB |
|
|
@ -1,10 +1,19 @@
|
|||
import NextAuth, { NextAuthOptions } from "next-auth";
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
import AzureADB2CProvider from "next-auth/providers/azure-ad-b2c";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
|
||||
import { db } from "@/app/db/db";
|
||||
import { user as userTable, User } from "@/app/db/schema/users";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const { GOOGLE_CLIENT_ID = "", GOOGLE_CLIENT_SECRET = "" } = process.env;
|
||||
const {
|
||||
AZURE_AD_B2C_TENANT_NAME = "",
|
||||
AZURE_AD_B2C_CLIENT_ID = "",
|
||||
AZURE_AD_B2C_CLIENT_SECRET = "",
|
||||
AZURE_AD_B2C_PRIMARY_USER_FLOW = "",
|
||||
} = process.env;
|
||||
|
||||
type OauthProvider = "google";
|
||||
|
||||
|
|
@ -26,6 +35,51 @@ export const AuthOptions: NextAuthOptions = {
|
|||
},
|
||||
},
|
||||
}),
|
||||
AzureADB2CProvider({
|
||||
tenantId: AZURE_AD_B2C_TENANT_NAME,
|
||||
clientId: AZURE_AD_B2C_CLIENT_ID,
|
||||
clientSecret: AZURE_AD_B2C_CLIENT_SECRET,
|
||||
primaryUserFlow: AZURE_AD_B2C_PRIMARY_USER_FLOW,
|
||||
authorization: {
|
||||
params: {
|
||||
scope: "openid profile offline_access",
|
||||
prompt: "login",
|
||||
},
|
||||
},
|
||||
}),
|
||||
CredentialsProvider({
|
||||
name: "Email Login",
|
||||
credentials: {
|
||||
email: {
|
||||
label: "Email",
|
||||
type: "email",
|
||||
},
|
||||
},
|
||||
async authorize(credentials, req) {
|
||||
if (!credentials || !credentials.email) {
|
||||
throw new Error("Email is required");
|
||||
}
|
||||
|
||||
const { email } = credentials;
|
||||
|
||||
// Query the database to find the user by email
|
||||
const dbUser = await db
|
||||
.select()
|
||||
.from(userTable)
|
||||
.where(eq(userTable.email, email));
|
||||
|
||||
// If the email exists, return the user object (no password check)
|
||||
if (dbUser.length === 1) {
|
||||
return {
|
||||
id: dbUser[0].id.toString(), // Convert bigint to string to avoid serialization issues
|
||||
email: dbUser[0].email,
|
||||
dbId: dbUser[0].id.toString(), // Ensure dbId is added and is a string
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
],
|
||||
pages: {
|
||||
signIn: "/",
|
||||
|
|
|
|||
81
src/app/api/energy-assessment-documents/route.ts
Normal file
81
src/app/api/energy-assessment-documents/route.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// pages/api/get-presigned-url.ts
|
||||
import S3 from "aws-sdk/clients/s3";
|
||||
import STS from "aws-sdk/clients/sts"; // Import STS for temporary credentials
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
// Validate the input
|
||||
const PresignedUrlBodySchema = z.object({
|
||||
fileKey: z.string(),
|
||||
});
|
||||
|
||||
// Function to get temporary credentials using GetSessionToken
|
||||
async function getTemporaryCredentials() {
|
||||
const sts = new STS({
|
||||
accessKeyId: process.env.RETROFIT_ENERGY_ASSESSMENTS_AWS_ACCESS_KEY, // Your permanent access key
|
||||
secretAccessKey: process.env.ENERGY_ASSESSMENTS_AWS_SECRET, // Your permanent secret access key
|
||||
region: process.env.PRESIGN_AWS_REGION,
|
||||
});
|
||||
|
||||
try {
|
||||
// Request temporary credentials with GetSessionToken
|
||||
const data = await sts.getSessionToken({ DurationSeconds: 900 }).promise(); // Token valid for 15 minutes
|
||||
|
||||
// Check if credentials are present
|
||||
if (!data.Credentials) {
|
||||
throw new Error("Failed to retrieve temporary credentials");
|
||||
}
|
||||
|
||||
return data.Credentials;
|
||||
} catch (error) {
|
||||
console.error("Error fetching temporary credentials:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// API handler
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
let validatedBody;
|
||||
|
||||
try {
|
||||
validatedBody = PresignedUrlBodySchema.parse(body);
|
||||
} catch (error) {
|
||||
console.error("Invalid input: ", error);
|
||||
return new NextResponse(JSON.stringify({ msg: "Invalid input" }), {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Get temporary credentials using GetSessionToken
|
||||
const credentials = await getTemporaryCredentials();
|
||||
|
||||
// Initialize S3 with temporary credentials
|
||||
const s3 = new S3({
|
||||
signatureVersion: "v4",
|
||||
region: process.env.PRESIGN_AWS_REGION,
|
||||
accessKeyId: credentials.AccessKeyId,
|
||||
secretAccessKey: credentials.SecretAccessKey,
|
||||
sessionToken: credentials.SessionToken, // Include session token
|
||||
});
|
||||
|
||||
const { fileKey } = validatedBody;
|
||||
|
||||
// Generate presigned URL valid for 5 minutes
|
||||
const preSignedUrl = await s3.getSignedUrl("getObject", {
|
||||
Bucket: process.env.RETROFIT_ENERGY_ASSESSMENTS_BUCKET,
|
||||
Key: fileKey,
|
||||
Expires: 5 * 60, // URL expiration in seconds
|
||||
});
|
||||
|
||||
return new NextResponse(JSON.stringify({ url: preSignedUrl }), {
|
||||
status: 200,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error generating presigned URL:", error);
|
||||
return new NextResponse(JSON.stringify({ msg: "Internal server error" }), {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
}
|
||||
155
src/app/api/portfolio/scenario/[scenarioId]/route.ts
Normal file
155
src/app/api/portfolio/scenario/[scenarioId]/route.ts
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { db } from "@/app/db/db";
|
||||
import { z } from "zod";
|
||||
import { formatNumber } from "@/app/utils";
|
||||
import { DataItem, ChartData } from "@/app/portfolio/[slug]/utils";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { scenario } from "@/app/db/schema/recommendations";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { scenarioId: string } }
|
||||
) {
|
||||
const scenarioId = params.scenarioId;
|
||||
|
||||
const data = await db
|
||||
.select({
|
||||
name: scenario.name,
|
||||
cost: scenario.cost,
|
||||
epcBreakdownPostRetrofit: scenario.epcBreakdownPostRetrofit,
|
||||
numberOfProperties: scenario.numberOfProperties,
|
||||
nUnitsToRetrofit: scenario.nUnitsToRetrofit,
|
||||
co2PerUnitPostRetrofit: scenario.co2PerUnitPostRetrofit,
|
||||
energyBillPerUnitPostRetrofit: scenario.energyBillPerUnitPostRetrofit,
|
||||
energyConsumptionPerUnitPostRetrofit:
|
||||
scenario.energyConsumptionPerUnitPostRetrofit,
|
||||
valuationImprovementPerUnit: scenario.valuationImprovementPerUnit,
|
||||
costPerUnit: scenario.costPerUnit,
|
||||
costPerCo2Saved: scenario.costPerCo2Saved,
|
||||
costPerSapPoint: scenario.costPerSapPoint,
|
||||
valuationReturnOnInvestment: scenario.valuationReturnOnInvestment,
|
||||
})
|
||||
.from(scenario)
|
||||
.where(eq(scenario.id, BigInt(scenarioId)));
|
||||
|
||||
if (data.length === 0) {
|
||||
throw new Error("Scenario not found");
|
||||
}
|
||||
|
||||
if (data.length > 1) {
|
||||
throw new Error("More than one scenario found");
|
||||
}
|
||||
|
||||
const scenarioName = data[0].name || "Default";
|
||||
|
||||
// Format the data we need for the overview
|
||||
const output: DataItem[] = [
|
||||
{
|
||||
title: "EPCs",
|
||||
scenarios: [
|
||||
{
|
||||
scenarioName: scenarioName,
|
||||
data: JSON.parse(
|
||||
data[0].epcBreakdownPostRetrofit || "[]"
|
||||
) as ChartData[],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "# Units",
|
||||
scenarios: [
|
||||
{
|
||||
scenarioName: scenarioName,
|
||||
data: String(data[0].numberOfProperties) || "",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "# Units to retrofit",
|
||||
scenarios: [
|
||||
{
|
||||
scenarioName: scenarioName,
|
||||
data: String(data[0].nUnitsToRetrofit) || "",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "CO2/unit",
|
||||
scenarios: [
|
||||
{
|
||||
scenarioName: scenarioName,
|
||||
data: data[0].co2PerUnitPostRetrofit || "",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Annual energy bill/unit",
|
||||
scenarios: [
|
||||
{
|
||||
scenarioName: scenarioName,
|
||||
data: data[0].energyBillPerUnitPostRetrofit || "",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Annual energy demand (kWh)/unit",
|
||||
scenarios: [
|
||||
{
|
||||
scenarioName: scenarioName,
|
||||
data: data[0].energyConsumptionPerUnitPostRetrofit || "",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Cost (£)",
|
||||
scenarios: [
|
||||
{
|
||||
scenarioName: scenarioName,
|
||||
data: "£" + formatNumber(data[0].cost || 0),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Cost (£)/unit",
|
||||
scenarios: [
|
||||
{ scenarioName: scenarioName, data: data[0].costPerUnit || "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "£ per CO2 reduction",
|
||||
scenarios: [
|
||||
{ scenarioName: scenarioName, data: data[0].costPerCo2Saved || "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "£ per SAP point",
|
||||
scenarios: [
|
||||
{ scenarioName: scenarioName, data: data[0].costPerSapPoint || "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title:
|
||||
"Potential valuation improvement/unit - highly indicative EPC effect*",
|
||||
scenarios: [
|
||||
{
|
||||
scenarioName: scenarioName,
|
||||
data: data[0].valuationImprovementPerUnit || "",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title:
|
||||
"Potential valuation return on investment - highly indicative EPC effect*",
|
||||
scenarios: [
|
||||
{
|
||||
scenarioName: scenarioName,
|
||||
data: data[0].valuationReturnOnInvestment || "",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return new NextResponse(JSON.stringify(output), {
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ export async function GET(
|
|||
with: {
|
||||
detailsEpc: {
|
||||
columns: {
|
||||
adjustedEnergyConsumption: true,
|
||||
currentEnergyDemand: true,
|
||||
co2Emissions: true,
|
||||
estimated: true,
|
||||
},
|
||||
|
|
@ -42,5 +42,9 @@ export async function GET(
|
|||
},
|
||||
});
|
||||
|
||||
if (!propertyMeta) {
|
||||
return new NextResponse(null, { status: 404 });
|
||||
}
|
||||
|
||||
return new NextResponse(JSON.stringify(propertyMeta, serializeBigInt));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export default function Beta() {
|
||||
return <div>This application is not ready for general usage</div>;
|
||||
return <div>You do not have access to this application currently</div>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,3 +18,31 @@ export function TanButton({
|
|||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function BrandButton({
|
||||
label,
|
||||
onClick,
|
||||
backgroundColor,
|
||||
}: {
|
||||
label: string;
|
||||
onClick: Dispatch<SetStateAction<any>>;
|
||||
backgroundColor: "brandblue" | "brandgold"; // Restrict backgroundColor to these two options
|
||||
}) {
|
||||
// Dictionary to map background colors to hover colors
|
||||
const hoverColors = {
|
||||
brandblue: "hover:bg-hoverblue",
|
||||
brandgold: "hover:bg-hovergold",
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`inline-flex justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2
|
||||
${backgroundColor === "brandblue" ? "bg-brandblue" : "bg-brandgold"}
|
||||
${hoverColors[backgroundColor]}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@ export default function StatusBadge({
|
|||
return (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger>
|
||||
<Badge className={statusConfig.class}>{statusConfig.text}</Badge>
|
||||
<Badge
|
||||
className={`truncate overflow-hidden whitespace-nowrap ${statusConfig.class}`}
|
||||
>
|
||||
{statusConfig.text}
|
||||
</Badge>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent>
|
||||
<div>
|
||||
|
|
@ -60,6 +64,12 @@ const statusColor: {
|
|||
hoverText: "This portfolio is currently in the assessment stage",
|
||||
propertyHoverText: "This property is currently in the assessment stage",
|
||||
},
|
||||
survey: {
|
||||
class: "bg-brandblue hover:bg-hoverblue",
|
||||
text: "survey",
|
||||
hoverText: "This portfolio is currently in the survey stage",
|
||||
propertyHoverText: "This property is currently in the survey stage",
|
||||
},
|
||||
tendering: {
|
||||
class: "bg-emerald-500 hover:bg-emerald-500",
|
||||
text: "tendering",
|
||||
|
|
|
|||
|
|
@ -52,13 +52,13 @@ export function EnergyEfficiencyImpactCard({
|
|||
interface SecondaryEnergyEfficiencyImpactCardProps {
|
||||
TotalCo2Savings: number;
|
||||
totalEnergyCostSavings: number;
|
||||
totalHeatDemandSavings: number;
|
||||
totalKwhSavings: number;
|
||||
}
|
||||
|
||||
export function SecondaryEnergyEfficiencyImpactCard({
|
||||
TotalCo2Savings,
|
||||
totalEnergyCostSavings,
|
||||
totalHeatDemandSavings,
|
||||
totalKwhSavings,
|
||||
}: SecondaryEnergyEfficiencyImpactCardProps) {
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -77,9 +77,7 @@ export function SecondaryEnergyEfficiencyImpactCard({
|
|||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Energy Savings</td>
|
||||
<td className="pr-2">
|
||||
{totalHeatDemandSavings.toFixed(0) + "kWh"}
|
||||
</td>
|
||||
<td className="pr-2">{totalKwhSavings.toFixed(0) + "kWh"}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ export default function EpcCard({
|
|||
expected = false,
|
||||
kwh = null,
|
||||
carbon = null,
|
||||
sap = null,
|
||||
}: {
|
||||
epcRating: string;
|
||||
fullMargin: boolean;
|
||||
expected?: boolean;
|
||||
kwh?: number | null;
|
||||
carbon?: number | null;
|
||||
sap?: string | null;
|
||||
}) {
|
||||
let marginClass = "";
|
||||
if (fullMargin) {
|
||||
|
|
@ -30,28 +32,35 @@ export default function EpcCard({
|
|||
return (
|
||||
<div
|
||||
className={
|
||||
"flex flex-col items-center p-4 shadow rounded-md max-w-xl justify-start text-gray-100 " +
|
||||
"flex flex-col items-center p-4 shadow rounded-md max-w-xl justify-start text-gray-100 " +
|
||||
bgColour
|
||||
}
|
||||
>
|
||||
<div className="text-xl font-bold mb-4 text-center">{title}</div>
|
||||
<div className="text-6xl font-bold ">{epcRating}</div>
|
||||
|
||||
{/* EPC Rating and SAP Rating */}
|
||||
<div className="flex items-baseline justify-center">
|
||||
{/* EPC Rating */}
|
||||
<div className="text-6xl font-bold">{epcRating}</div>
|
||||
|
||||
{/* SAP Rating (only if sap is provided) */}
|
||||
{sap !== null && (
|
||||
<div className="text-lg font-medium ml-2 text-gray-100">{sap}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(kwh || carbon) && (
|
||||
<table className="mt-6">
|
||||
<tbody>
|
||||
{kwh && (
|
||||
<tr>
|
||||
{" "}
|
||||
{/* Added vertical padding to each row */}
|
||||
<td className="text-gray-50">{kwh.toFixed(0)} kWh</td>
|
||||
</tr>
|
||||
)}
|
||||
{carbon && (
|
||||
<tr>
|
||||
{" "}
|
||||
<td className="text-gray-50 py-2">
|
||||
{carbon}t CO <sub>2</sub>
|
||||
{carbon}t CO<sub>2</sub>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -16,17 +16,22 @@ import {
|
|||
TableRow,
|
||||
} from "@/app/shadcn_components/ui/table";
|
||||
|
||||
import { Feature, GeneralFeature } from "@/app/db/schema/property";
|
||||
import {
|
||||
Feature,
|
||||
GeneralFeature,
|
||||
NonInstrusiveFeature,
|
||||
} from "@/app/db/schema/property";
|
||||
|
||||
interface DataTableProps<T extends Feature | GeneralFeature> {
|
||||
interface DataTableProps<
|
||||
T extends Feature | GeneralFeature | NonInstrusiveFeature
|
||||
> {
|
||||
columns: ColumnDef<T>[];
|
||||
data: T[];
|
||||
}
|
||||
|
||||
export default function FeatureTable<T extends Feature | GeneralFeature>({
|
||||
data,
|
||||
columns,
|
||||
}: DataTableProps<T>) {
|
||||
export default function FeatureTable<
|
||||
T extends Feature | GeneralFeature | NonInstrusiveFeature
|
||||
>({ data, columns }: DataTableProps<T>) {
|
||||
// Initialise the table
|
||||
|
||||
const table = useReactTable({
|
||||
|
|
@ -41,18 +46,20 @@ export default function FeatureTable<T extends Feature | GeneralFeature>({
|
|||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead
|
||||
key={header.id}
|
||||
style={{
|
||||
// Style the header to match the column width, if passed
|
||||
width: `${header.getSize()}px`,
|
||||
}}
|
||||
>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { Feature, GeneralFeature, Rating } from "@/app/db/schema/property";
|
||||
import {
|
||||
Feature,
|
||||
GeneralFeature,
|
||||
NonInstrusiveFeature,
|
||||
Rating,
|
||||
} from "@/app/db/schema/property";
|
||||
import { Badge } from "@/app/shadcn_components/ui/badge";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
|
|
@ -42,9 +47,22 @@ export const generalColumns: ColumnDef<GeneralFeature>[] = [
|
|||
{
|
||||
accessorKey: "feature",
|
||||
header: "Feature",
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
},
|
||||
];
|
||||
|
||||
export const nonInstrusiveColumns: ColumnDef<NonInstrusiveFeature>[] = [
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: "Feature",
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "note",
|
||||
header: "Recorded Observation",
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -12,9 +12,12 @@ const selectionStyling =
|
|||
"shadow active:shadow active:bg-brandmidblue w-full border rounded p-4 cursor-pointer text-gray-900 bg-gray-100 hover:bg-hoverblue hover:text-gray-100 transition-colors rounded-md flex flex-col justify-start";
|
||||
const noSelectionStyling =
|
||||
"shadow active:shadow active:bg-brandmidblue w-full border rounded p-4 cursor-pointer text-gray-300 bg-white hover:bg-hoverblue hover:text-gray-100 transition-colors rounded-md flex flex-col justify-start";
|
||||
const alreadyInstalledStyling =
|
||||
"shadow active:shadow w-full border rounded p-4 cursor-pointer text-gray-900 bg-gray-100transition-colors rounded-md flex flex-col justify-start";
|
||||
|
||||
const TitleMap = {
|
||||
mechanical_ventilation: "Mechanical Ventilation",
|
||||
trickle_vents: "Trickle Vents",
|
||||
sealing_open_fireplace: "Sealing Open Fireplace",
|
||||
low_energy_lighting: "Low Energy Lighting",
|
||||
// Walls
|
||||
|
|
@ -31,12 +34,23 @@ const TitleMap = {
|
|||
exposed_floor_insulation: "Exposed Floor Insulation",
|
||||
// Windows
|
||||
windows_glazing: "Window Glazing",
|
||||
mixed_glazing: "Mixed - Secondary and Double Glazing",
|
||||
// Solar pv
|
||||
solar_pv: "Solar Photovoltaic Panels System",
|
||||
// Heating
|
||||
heating: "Heating Systems",
|
||||
heating_control: "Heating Controls",
|
||||
secondary_heating: "Secondary Heating System",
|
||||
// Hot water tank
|
||||
hot_water_tank_insulation: "Hot Water Tank Insulation",
|
||||
// Default options when no recommendation is selected
|
||||
wall_insulation: "Wall Insulation",
|
||||
floor_insulation: "Floor Insulation",
|
||||
roof_insulation: "Roof Insulation",
|
||||
// Cylinder thermostat
|
||||
cylinder_thermostat: "Cylinder Thermostat",
|
||||
// Draught proofing
|
||||
draught_proofing: "Draught Proofing",
|
||||
};
|
||||
|
||||
type RecommendationCardProps = {
|
||||
|
|
@ -59,9 +73,9 @@ type RecommendationCardProps = {
|
|||
setEnergyCostSavingsMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
energyCostSavingsMap: RecommendationMetricMap;
|
||||
setTotalEnergyCostSavings: Dispatch<SetStateAction<number>>;
|
||||
setHeatDemandMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
heatDemandMap: RecommendationMetricMap;
|
||||
setTotalHeatDemandSavings: Dispatch<SetStateAction<number>>;
|
||||
setKwhSavingsMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
kwhSavingsMap: RecommendationMetricMap;
|
||||
setTotalKwhSavings: Dispatch<SetStateAction<number>>;
|
||||
};
|
||||
|
||||
export default function RecommendationCard({
|
||||
|
|
@ -84,14 +98,19 @@ export default function RecommendationCard({
|
|||
setEnergyCostSavingsMap,
|
||||
energyCostSavingsMap,
|
||||
setTotalEnergyCostSavings,
|
||||
setHeatDemandMap,
|
||||
heatDemandMap,
|
||||
setTotalHeatDemandSavings,
|
||||
setKwhSavingsMap,
|
||||
kwhSavingsMap,
|
||||
setTotalKwhSavings,
|
||||
}: RecommendationCardProps) {
|
||||
const defaultComponent = recommendationData.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) as Recommendation;
|
||||
|
||||
// A recommendation type could have no default recommendation, so we need to check if it exists
|
||||
const alreadyInstalled = defaultComponent
|
||||
? defaultComponent.alreadyInstalled
|
||||
: false;
|
||||
|
||||
const [cardComponent, setCardComponent] =
|
||||
useState<Recommendation>(defaultComponent);
|
||||
|
||||
|
|
@ -107,14 +126,39 @@ export default function RecommendationCard({
|
|||
return TitleMap[recommendationType];
|
||||
};
|
||||
|
||||
// Determine the className based on alreadyInstalled or cardComponent existence
|
||||
const cardClassName = alreadyInstalled
|
||||
? alreadyInstalledStyling
|
||||
: cardComponent
|
||||
? selectionStyling
|
||||
: noSelectionStyling;
|
||||
|
||||
const optionTextClassName = alreadyInstalled
|
||||
? "text-brandgold"
|
||||
: "text-blue-500 hover:text-blue-300";
|
||||
|
||||
const optionsText = alreadyInstalled
|
||||
? "Already installed"
|
||||
: cardComponent
|
||||
? "Click for more options"
|
||||
: "Click to select";
|
||||
|
||||
const openModal = () => {
|
||||
// If the card is already installed, we don't want to open the modal
|
||||
if (alreadyInstalled) {
|
||||
return;
|
||||
}
|
||||
setModalIsOpen(true);
|
||||
};
|
||||
|
||||
// If the measure is already installed, we change the font colour to gold
|
||||
const titleClassName = alreadyInstalled
|
||||
? "text-brandgold font-bold mb-4 text-lg"
|
||||
: "font-bold mb-4 text-lg";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cardComponent ? selectionStyling : noSelectionStyling}
|
||||
onClick={() => {
|
||||
setModalIsOpen(true);
|
||||
}}
|
||||
>
|
||||
<h2 className="font-bold mb-4 text-lg">{getTitle()}</h2>
|
||||
<div className={cardClassName} onClick={openModal}>
|
||||
<h2 className={titleClassName}>{getTitle()}</h2>
|
||||
<div className="mb-3">
|
||||
{cardComponent ? (
|
||||
cardComponent.description
|
||||
|
|
@ -122,9 +166,7 @@ export default function RecommendationCard({
|
|||
<div className="text-red-500">No measure selected</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-blue-500 hover:text-blue-300">
|
||||
{cardComponent ? "Click for more options" : "Click to select"}
|
||||
</div>
|
||||
<div className={optionTextClassName}>{optionsText}</div>
|
||||
{cardComponent ? (
|
||||
<table className="w-full text-left">
|
||||
<tbody>
|
||||
|
|
@ -184,10 +226,10 @@ export default function RecommendationCard({
|
|||
setEnergyCostSavingsMap={setEnergyCostSavingsMap}
|
||||
energyCostSavingsMap={energyCostSavingsMap}
|
||||
setTotalEnergyCostSavings={setTotalEnergyCostSavings}
|
||||
// Heat Demand
|
||||
setHeatDemandMap={setHeatDemandMap}
|
||||
heatDemandMap={heatDemandMap}
|
||||
setTotalHeatDemandSavings={setTotalHeatDemandSavings}
|
||||
// kWh Savings
|
||||
setKwhSavingsMap={setKwhSavingsMap}
|
||||
kwhSavingsMap={kwhSavingsMap}
|
||||
setTotalKwhSavings={setTotalKwhSavings}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const typeToCategoryMap: { [key in RecommendationType]?: RecommendationType } =
|
|||
solid_floor_insulation: "floor_insulation",
|
||||
exposed_floor_insulation: "floor_insulation",
|
||||
windows_glazing: "windows_glazing",
|
||||
heating: "heating",
|
||||
};
|
||||
|
||||
const emptyImpactState = {
|
||||
|
|
@ -42,7 +43,7 @@ const emptyImpactState = {
|
|||
labourDays: 0,
|
||||
co2EquivalentSavings: 0,
|
||||
energyCostSavings: 0,
|
||||
adjustedHeatDemand: 0,
|
||||
kwhSavings: 0,
|
||||
};
|
||||
|
||||
export default function RecommendationContainer({
|
||||
|
|
@ -101,6 +102,46 @@ export default function RecommendationContainer({
|
|||
(rec: Recommendation) => rec.default
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultHeatingRecommendations =
|
||||
categorizedRecommendations.heating?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultHeatingControlRecommendations =
|
||||
categorizedRecommendations.heating_control?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultHotWaterTankRecommendations =
|
||||
categorizedRecommendations.hot_water_tank_insulation?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultSecondaryHeatingRecommendations =
|
||||
categorizedRecommendations.secondary_heating?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultCylinderThermostatRecommendations =
|
||||
categorizedRecommendations.cylinder_thermostat?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultTrickleVentsRecommendations =
|
||||
categorizedRecommendations.trickle_vents?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultMixedGlazingRecommendations =
|
||||
categorizedRecommendations.mixed_glazing?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultDraughtProofingRecommendations =
|
||||
categorizedRecommendations.draught_proofing?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || emptyImpactState;
|
||||
|
||||
const [costMap, setCostMap] = useState<RecommendationMetricMap>({
|
||||
wall_insulation: defaultWallsRecommendations.estimatedCost || 0,
|
||||
floor_insulation: defaultFloorRecommendations.estimatedCost || 0,
|
||||
|
|
@ -111,6 +152,17 @@ export default function RecommendationContainer({
|
|||
low_energy_lighting: defaultLightingRecommendations.estimatedCost || 0,
|
||||
windows_glazing: defaultWindowsRecommendations.estimatedCost || 0,
|
||||
solar_pv: defaultSolarRecommendations.estimatedCost || 0,
|
||||
heating: defaultHeatingRecommendations.estimatedCost || 0,
|
||||
hot_water_tank_insulation:
|
||||
defaultHotWaterTankRecommendations.estimatedCost || 0,
|
||||
heating_control: defaultHeatingControlRecommendations.estimatedCost || 0,
|
||||
secondary_heating:
|
||||
defaultSecondaryHeatingRecommendations.estimatedCost || 0,
|
||||
cylinder_thermostat:
|
||||
defaultCylinderThermostatRecommendations.estimatedCost || 0,
|
||||
trickle_vents: defaultTrickleVentsRecommendations.estimatedCost || 0,
|
||||
mixed_glazing: defaultMixedGlazingRecommendations.estimatedCost || 0,
|
||||
draught_proofing: defaultDraughtProofingRecommendations.estimatedCost || 0,
|
||||
});
|
||||
|
||||
const [sapMap, setSapMap] = useState<RecommendationMetricMap>({
|
||||
|
|
@ -122,6 +174,16 @@ export default function RecommendationContainer({
|
|||
low_energy_lighting: defaultLightingRecommendations.sapPoints || 0,
|
||||
windows_glazing: defaultWindowsRecommendations.sapPoints || 0,
|
||||
solar_pv: defaultSolarRecommendations.sapPoints || 0,
|
||||
heating: defaultHeatingRecommendations.sapPoints || 0,
|
||||
hot_water_tank_insulation:
|
||||
defaultHotWaterTankRecommendations.sapPoints || 0,
|
||||
heating_control: defaultHeatingControlRecommendations.sapPoints || 0,
|
||||
secondary_heating: defaultSecondaryHeatingRecommendations.sapPoints || 0,
|
||||
cylinder_thermostat:
|
||||
defaultCylinderThermostatRecommendations.sapPoints || 0,
|
||||
trickle_vents: defaultTrickleVentsRecommendations.sapPoints || 0,
|
||||
mixed_glazing: defaultMixedGlazingRecommendations.sapPoints || 0,
|
||||
draught_proofing: defaultDraughtProofingRecommendations.sapPoints || 0,
|
||||
});
|
||||
|
||||
const [labourDaysMap, setLabourDaysMap] = useState<RecommendationMetricMap>({
|
||||
|
|
@ -133,6 +195,16 @@ export default function RecommendationContainer({
|
|||
low_energy_lighting: defaultLightingRecommendations.labourDays || 0,
|
||||
windows_glazing: defaultWindowsRecommendations.labourDays || 0,
|
||||
solar_pv: defaultSolarRecommendations.labourDays || 0,
|
||||
heating: defaultHeatingRecommendations.labourDays || 0,
|
||||
hot_water_tank_insulation:
|
||||
defaultHotWaterTankRecommendations.labourDays || 0,
|
||||
heating_control: defaultHeatingControlRecommendations.labourDays || 0,
|
||||
secondary_heating: defaultSecondaryHeatingRecommendations.labourDays || 0,
|
||||
cylinder_thermostat:
|
||||
defaultCylinderThermostatRecommendations.labourDays || 0,
|
||||
trickle_vents: defaultTrickleVentsRecommendations.labourDays || 0,
|
||||
mixed_glazing: defaultMixedGlazingRecommendations.labourDays || 0,
|
||||
draught_proofing: defaultDraughtProofingRecommendations.labourDays || 0,
|
||||
});
|
||||
|
||||
const [co2SavingsMap, setCo2SavingsMap] = useState<RecommendationMetricMap>({
|
||||
|
|
@ -147,6 +219,19 @@ export default function RecommendationContainer({
|
|||
defaultLightingRecommendations.co2EquivalentSavings || 0,
|
||||
windows_glazing: defaultWindowsRecommendations.co2EquivalentSavings || 0,
|
||||
solar_pv: defaultSolarRecommendations.co2EquivalentSavings || 0,
|
||||
heating: defaultHeatingRecommendations.co2EquivalentSavings || 0,
|
||||
hot_water_tank_insulation:
|
||||
defaultHotWaterTankRecommendations.co2EquivalentSavings || 0,
|
||||
heating_control:
|
||||
defaultHeatingControlRecommendations.co2EquivalentSavings || 0,
|
||||
secondary_heating:
|
||||
defaultSecondaryHeatingRecommendations.co2EquivalentSavings || 0,
|
||||
cylinder_thermostat:
|
||||
defaultCylinderThermostatRecommendations.co2EquivalentSavings || 0,
|
||||
trickle_vents: defaultTrickleVentsRecommendations.co2EquivalentSavings || 0,
|
||||
mixed_glazing: defaultMixedGlazingRecommendations.co2EquivalentSavings || 0,
|
||||
draught_proofing:
|
||||
defaultDraughtProofingRecommendations.co2EquivalentSavings || 0,
|
||||
});
|
||||
|
||||
const [energyCostSavingsMap, setEnergyCostSavingsMap] =
|
||||
|
|
@ -162,19 +247,40 @@ export default function RecommendationContainer({
|
|||
defaultLightingRecommendations.energyCostSavings || 0,
|
||||
windows_glazing: defaultWindowsRecommendations.energyCostSavings || 0,
|
||||
solar_pv: defaultSolarRecommendations.energyCostSavings || 0,
|
||||
heating: defaultHeatingRecommendations.energyCostSavings || 0,
|
||||
hot_water_tank_insulation:
|
||||
defaultHotWaterTankRecommendations.energyCostSavings || 0,
|
||||
heating_control:
|
||||
defaultHeatingControlRecommendations.energyCostSavings || 0,
|
||||
secondary_heating:
|
||||
defaultSecondaryHeatingRecommendations.energyCostSavings || 0,
|
||||
cylinder_thermostat:
|
||||
defaultCylinderThermostatRecommendations.energyCostSavings || 0,
|
||||
trickle_vents: defaultTrickleVentsRecommendations.energyCostSavings || 0,
|
||||
mixed_glazing: defaultMixedGlazingRecommendations.energyCostSavings || 0,
|
||||
draught_proofing:
|
||||
defaultDraughtProofingRecommendations.energyCostSavings || 0,
|
||||
});
|
||||
|
||||
const [heatDemandMap, setHeatDemandMap] = useState<RecommendationMetricMap>({
|
||||
wall_insulation: defaultWallsRecommendations.adjustedHeatDemand || 0,
|
||||
floor_insulation: defaultFloorRecommendations.adjustedHeatDemand || 0,
|
||||
roof_insulation: defaultRoofRecommendations.adjustedHeatDemand || 0,
|
||||
mechanical_ventilation:
|
||||
defaultVentiliationRecommendations.adjustedHeatDemand || 0,
|
||||
sealing_open_fireplace:
|
||||
defaultFireplaceRecommendations.adjustedHeatDemand || 0,
|
||||
low_energy_lighting: defaultLightingRecommendations.adjustedHeatDemand || 0,
|
||||
windows_glazing: defaultWindowsRecommendations.adjustedHeatDemand || 0,
|
||||
solar_pv: defaultSolarRecommendations.adjustedHeatDemand || 0,
|
||||
const [kwhSavingsMap, setKwhSavingsMap] = useState<RecommendationMetricMap>({
|
||||
wall_insulation: defaultWallsRecommendations.kwhSavings || 0,
|
||||
floor_insulation: defaultFloorRecommendations.kwhSavings || 0,
|
||||
roof_insulation: defaultRoofRecommendations.kwhSavings || 0,
|
||||
mechanical_ventilation: defaultVentiliationRecommendations.kwhSavings || 0,
|
||||
sealing_open_fireplace: defaultFireplaceRecommendations.kwhSavings || 0,
|
||||
low_energy_lighting: defaultLightingRecommendations.kwhSavings || 0,
|
||||
windows_glazing: defaultWindowsRecommendations.kwhSavings || 0,
|
||||
solar_pv: defaultSolarRecommendations.kwhSavings || 0,
|
||||
heating: defaultHeatingRecommendations.kwhSavings || 0,
|
||||
hot_water_tank_insulation:
|
||||
defaultHotWaterTankRecommendations.kwhSavings || 0,
|
||||
heating_control: defaultHeatingControlRecommendations.kwhSavings || 0,
|
||||
secondary_heating: defaultSecondaryHeatingRecommendations.kwhSavings || 0,
|
||||
cylinder_thermostat:
|
||||
defaultCylinderThermostatRecommendations.kwhSavings || 0,
|
||||
trickle_vents: defaultTrickleVentsRecommendations.kwhSavings || 0,
|
||||
mixed_glazing: defaultMixedGlazingRecommendations.kwhSavings || 0,
|
||||
draught_proofing: defaultDraughtProofingRecommendations.kwhSavings || 0,
|
||||
});
|
||||
|
||||
const [totalEstimatedCost, setTotalEstimatedCost] = useState(
|
||||
|
|
@ -197,8 +303,8 @@ export default function RecommendationContainer({
|
|||
sumRecommendationMetricMap(energyCostSavingsMap)
|
||||
);
|
||||
|
||||
const [totalHeatDemandSavings, setTotalHeatDemandSavings] = useState(
|
||||
sumRecommendationMetricMap(heatDemandMap)
|
||||
const [totalKwhSavings, setTotalKwhSavings] = useState(
|
||||
sumRecommendationMetricMap(kwhSavingsMap)
|
||||
);
|
||||
|
||||
const currentEpcRating = propertyMeta.currentEpcRating;
|
||||
|
|
@ -228,7 +334,7 @@ export default function RecommendationContainer({
|
|||
<SecondaryEnergyEfficiencyImpactCard
|
||||
TotalCo2Savings={totalCo2Savings}
|
||||
totalEnergyCostSavings={totalEnergyCostSavings}
|
||||
totalHeatDemandSavings={totalHeatDemandSavings}
|
||||
totalKwhSavings={totalKwhSavings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -265,10 +371,10 @@ export default function RecommendationContainer({
|
|||
setEnergyCostSavingsMap={setEnergyCostSavingsMap}
|
||||
energyCostSavingsMap={energyCostSavingsMap}
|
||||
setTotalEnergyCostSavings={setTotalEnergyCostSavings}
|
||||
// Heat Demand
|
||||
setHeatDemandMap={setHeatDemandMap}
|
||||
heatDemandMap={heatDemandMap}
|
||||
setTotalHeatDemandSavings={setTotalHeatDemandSavings}
|
||||
// kwh Savings
|
||||
setKwhSavingsMap={setKwhSavingsMap}
|
||||
kwhSavingsMap={kwhSavingsMap}
|
||||
setTotalKwhSavings={setTotalKwhSavings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ interface RecommendationModalProps {
|
|||
setEnergyCostSavingsMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
energyCostSavingsMap: RecommendationMetricMap;
|
||||
setTotalEnergyCostSavings: Dispatch<SetStateAction<number>>;
|
||||
setHeatDemandMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
heatDemandMap: RecommendationMetricMap;
|
||||
setTotalHeatDemandSavings: Dispatch<SetStateAction<number>>;
|
||||
setKwhSavingsMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
kwhSavingsMap: RecommendationMetricMap;
|
||||
setTotalKwhSavings: Dispatch<SetStateAction<number>>;
|
||||
}
|
||||
|
||||
const TitleMap = {
|
||||
|
|
@ -70,9 +70,9 @@ export default function RecommendationModal({
|
|||
setEnergyCostSavingsMap,
|
||||
energyCostSavingsMap,
|
||||
setTotalEnergyCostSavings,
|
||||
setHeatDemandMap,
|
||||
heatDemandMap,
|
||||
setTotalHeatDemandSavings,
|
||||
setKwhSavingsMap,
|
||||
kwhSavingsMap,
|
||||
setTotalKwhSavings,
|
||||
}: RecommendationModalProps) {
|
||||
const [saveButtonDisabled, setSaveButtonDisabled] = useState(true);
|
||||
|
||||
|
|
@ -165,15 +165,15 @@ export default function RecommendationModal({
|
|||
);
|
||||
|
||||
// Update the heat demand savings map
|
||||
const newHeatDemandMap = {
|
||||
...heatDemandMap,
|
||||
[title]: recommendationData[newIndex]?.heatDemand || 0,
|
||||
const newKwhSavingsMap = {
|
||||
...kwhSavingsMap,
|
||||
[title]: recommendationData[newIndex]?.kwhSavings || 0,
|
||||
};
|
||||
|
||||
setHeatDemandMap(newHeatDemandMap);
|
||||
setKwhSavingsMap(newKwhSavingsMap);
|
||||
|
||||
// update the heat demand savings sum
|
||||
setTotalHeatDemandSavings(sumRecommendationMetricMap(newHeatDemandMap));
|
||||
setTotalKwhSavings(sumRecommendationMetricMap(newKwhSavingsMap));
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
NewspaperIcon,
|
||||
HomeModernIcon,
|
||||
WrenchScrewdriverIcon,
|
||||
LightBulbIcon,
|
||||
SunIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import {
|
||||
NavigationMenu,
|
||||
|
|
@ -29,10 +29,6 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) {
|
|||
console.log("Settings were clicked, implement me");
|
||||
}
|
||||
|
||||
function handleClickPortfolioPlan() {
|
||||
console.log("Opt Plan was clicked, implement me");
|
||||
}
|
||||
|
||||
const preAssessmentReportButton = (
|
||||
<NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
|
|
@ -43,6 +39,26 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) {
|
|||
</NavigationMenuLink>
|
||||
);
|
||||
|
||||
const energyAssessmentsReportButton = (
|
||||
<NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
href={`/portfolio/${portfolioId}/building-passport/${propertyId}/energy-assessment`}
|
||||
>
|
||||
<NewspaperIcon className="h-4 w-4 mr-2" />
|
||||
Energy Assessment
|
||||
</NavigationMenuLink>
|
||||
);
|
||||
|
||||
const solarAnalysisButton = (
|
||||
<NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
href={`/portfolio/${portfolioId}/building-passport/${propertyId}/solar-analysis`}
|
||||
>
|
||||
<SunIcon className="h-4 w-4 mr-2" />
|
||||
Solar Analysis
|
||||
</NavigationMenuLink>
|
||||
);
|
||||
|
||||
const recommendationsButton = (
|
||||
<NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
|
|
@ -65,14 +81,9 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) {
|
|||
|
||||
<NavigationMenuList>
|
||||
{preAssessmentReportButton}
|
||||
{solarAnalysisButton}
|
||||
{recommendationsButton}
|
||||
{/* <NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
href={`/portfolio/${portfolioId}/building-passport/${propertyId}/plan-optimiser`}
|
||||
>
|
||||
<LightBulbIcon className="h-4 w-4 mr-2" />
|
||||
Plan optimiser
|
||||
</NavigationMenuLink> */}
|
||||
{energyAssessmentsReportButton}
|
||||
<NavigationMenuItem
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
onClick={handleClickSettings}
|
||||
|
|
|
|||
191
src/app/components/portfolio/SummaryBox.tsx
Normal file
191
src/app/components/portfolio/SummaryBox.tsx
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
"use client";
|
||||
import { useState } from "react";
|
||||
import { convertDaysToWorkingWeeks, formatNumber } from "@/app/utils";
|
||||
|
||||
interface SummaryBoxProps {
|
||||
scenarios: Array<{
|
||||
id: bigint;
|
||||
name: string;
|
||||
budget: number | null;
|
||||
totalCost: number | null;
|
||||
co2EquivalentSavings: number | null;
|
||||
propertyValuationIncrease: number | null;
|
||||
energySavings: number | null;
|
||||
energyCostSavings: number | null;
|
||||
labourDays: number | null;
|
||||
isDefault: boolean;
|
||||
}>;
|
||||
numProperties: number;
|
||||
}
|
||||
|
||||
function SummaryBox({ scenarios, numProperties }: SummaryBoxProps) {
|
||||
// Get the default scenario
|
||||
const defaultScenario =
|
||||
scenarios.find((scenario) => scenario.isDefault) || scenarios[0];
|
||||
|
||||
const [selectedScenarioId, setSelectedScenarioId] = useState(
|
||||
Number(defaultScenario.id)
|
||||
);
|
||||
|
||||
const [budgetFormatted, setBudgetFormatted] = useState(
|
||||
formatBudget(defaultScenario.budget)
|
||||
);
|
||||
const [totalCostFormatted, setTotalCostFormatted] = useState(
|
||||
formatMoney(defaultScenario.totalCost)
|
||||
);
|
||||
const [totalValueIncreaseFormatted, setTotalValueIncreaseFormatted] =
|
||||
useState(formatMoney(defaultScenario.propertyValuationIncrease));
|
||||
const [energyCostSavingsFormatted, setEnergyCostSavingsFormatted] = useState(
|
||||
formatMoney(defaultScenario.energyCostSavings)
|
||||
);
|
||||
const [co2EquivalentSavingsFormatted, setCo2EquivalentSavingsFormatted] =
|
||||
useState(formatCo2(defaultScenario.co2EquivalentSavings));
|
||||
const [energySavingsFormatted, setEnergySavingsFormatted] = useState(
|
||||
formatKwh(defaultScenario.energySavings)
|
||||
);
|
||||
|
||||
const handleScenarioChange = (scenarioId: string) => {
|
||||
setSelectedScenarioId(Number(scenarioId));
|
||||
const selectedScenario = scenarios.find(
|
||||
(scenario) => Number(scenario.id) === Number(scenarioId)
|
||||
);
|
||||
|
||||
if (!selectedScenario) {
|
||||
return;
|
||||
}
|
||||
|
||||
setBudgetFormatted(formatBudget(selectedScenario.budget));
|
||||
setTotalCostFormatted(formatMoney(selectedScenario.totalCost));
|
||||
setTotalValueIncreaseFormatted(
|
||||
formatMoney(selectedScenario.propertyValuationIncrease)
|
||||
);
|
||||
setEnergyCostSavingsFormatted(
|
||||
formatMoney(selectedScenario.energyCostSavings)
|
||||
);
|
||||
setCo2EquivalentSavingsFormatted(
|
||||
formatCo2(selectedScenario.co2EquivalentSavings)
|
||||
);
|
||||
setEnergySavingsFormatted(formatKwh(selectedScenario.energySavings));
|
||||
};
|
||||
|
||||
function formatMoney(amount: number | null) {
|
||||
if (amount === 0) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return amount === null ? "-" : "£" + formatNumber(amount);
|
||||
}
|
||||
|
||||
function formatBudget(budget: number | null) {
|
||||
return budget === null ? "Not set" : formatMoney(budget);
|
||||
}
|
||||
|
||||
function formatHours(hours: number | null) {
|
||||
return hours === null ? "0 hours" : Math.round(hours) + " hours";
|
||||
}
|
||||
|
||||
function formatCo2(co2: number | null) {
|
||||
return co2 === null ? "-" : co2.toFixed(1) + " tonnes";
|
||||
}
|
||||
|
||||
function formatKwh(energy: number | null) {
|
||||
return energy === null ? "-" : formatNumber(energy) + " kWh";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-white rounded-lg leading-relaxed">
|
||||
<h2 className="text-2xl font-bold mb-4 text-brandblue text-center">
|
||||
Portfolio Summary
|
||||
</h2>
|
||||
<div className="mb-4 flex items-center justify-center">
|
||||
<select
|
||||
id="scenario-select"
|
||||
value={selectedScenarioId}
|
||||
onChange={(e) => handleScenarioChange(e.target.value)}
|
||||
className="p-2 border rounded w-full"
|
||||
>
|
||||
{scenarios.map((scenario) => (
|
||||
<option key={String(scenario.id)} value={String(scenario.id)}>
|
||||
{scenario.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-1 gap-6">
|
||||
<div className="p-4 bg-gray-50 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-brandblue mb-2">
|
||||
Work Package
|
||||
</h3>
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="text-brandblue">Total Budget</td>
|
||||
<td className="text-brandblue text-end">{budgetFormatted}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-brandblue">Total Cost</td>
|
||||
<td className="text-brandblue text-end">
|
||||
{totalCostFormatted}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-brandblue">Total properties</td>
|
||||
<td className="text-brandblue text-end">{numProperties}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="p-4 bg-gray-50 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-brandblue mb-2">
|
||||
Environmental Impact
|
||||
</h3>
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="text-brandblue">
|
||||
Annual{" "}
|
||||
<span>
|
||||
CO<sub>2</sub>
|
||||
</span>{" "}
|
||||
Savings
|
||||
</td>
|
||||
<td className="text-brandblue text-end">
|
||||
{co2EquivalentSavingsFormatted}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-brandblue">Annual Energy Savings</td>
|
||||
<td className="text-brandblue text-end">
|
||||
{energySavingsFormatted}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="p-4 bg-gray-50 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-brandblue mb-2">
|
||||
Financial Impact
|
||||
</h3>
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="text-brandblue">Annual Energy Bill Reduction</td>
|
||||
<td className="text-brandblue text-end">
|
||||
{energyCostSavingsFormatted}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-brandblue">Total Value Increase</td>
|
||||
<td className="text-brandblue text-end">
|
||||
{totalValueIncreaseFormatted}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SummaryBox;
|
||||
|
|
@ -4,6 +4,7 @@ import {
|
|||
Cog6ToothIcon,
|
||||
CalculatorIcon,
|
||||
BuildingOfficeIcon,
|
||||
ChartBarIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import {
|
||||
NavigationMenu,
|
||||
|
|
@ -39,6 +40,10 @@ export function Toolbar({ portfolioId }: ToolbarProps) {
|
|||
router.push(`/portfolio/${portfolioId}`);
|
||||
}
|
||||
|
||||
function handleClickSummary() {
|
||||
router.push(`/portfolio/${portfolioId}/summary`);
|
||||
}
|
||||
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
|
|
@ -53,12 +58,20 @@ export function Toolbar({ portfolioId }: ToolbarProps) {
|
|||
</NavigationMenuItem>
|
||||
|
||||
<NavigationMenuItem
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
onClick={handleClickSummary}
|
||||
>
|
||||
<ChartBarIcon className="h-4 w-4 mr-2" />
|
||||
Summary
|
||||
</NavigationMenuItem>
|
||||
|
||||
{/* <NavigationMenuItem
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
onClick={handleClickPortfolioPlan}
|
||||
>
|
||||
<CalculatorIcon className="h-4 w-4 mr-2" />
|
||||
Portfolio Plan
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuItem> */}
|
||||
|
||||
<NavigationMenuItem
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
|
|
|
|||
41
src/app/components/portfolio/summary/EpcBarChart.tsx
Normal file
41
src/app/components/portfolio/summary/EpcBarChart.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { BarChart } from "@tremor/react";
|
||||
|
||||
const dataFormatter = (number: number) =>
|
||||
Intl.NumberFormat("us").format(number).toString();
|
||||
|
||||
const EpcBarChart = ({
|
||||
chartdata,
|
||||
}: {
|
||||
chartdata: {
|
||||
name: string;
|
||||
A?: number;
|
||||
B?: number;
|
||||
C?: number;
|
||||
D?: number;
|
||||
E?: number;
|
||||
F?: number;
|
||||
G?: number;
|
||||
}[];
|
||||
}) => (
|
||||
<BarChart
|
||||
className="w-64 h-40"
|
||||
data={chartdata}
|
||||
index="name"
|
||||
categories={["G", "F", "E", "D", "C", "B", "A"]} // Each treated as a separate series
|
||||
colors={[
|
||||
"#e41e3b", // Color for 'G'
|
||||
"#ef8026", // Color for 'F'
|
||||
"#f3a96a", // Color for 'E'
|
||||
"#f7cd14", // Color for 'D'
|
||||
"#8dbd40", // Color for 'C'
|
||||
"#2da55c", // Color for 'B'
|
||||
"#117d58", // Color for 'A'
|
||||
]}
|
||||
valueFormatter={dataFormatter}
|
||||
yAxisWidth={48}
|
||||
stack={true}
|
||||
showLegend={false}
|
||||
/>
|
||||
);
|
||||
|
||||
export default EpcBarChart;
|
||||
101
src/app/components/portfolio/summary/SelectComparisonModal.tsx
Normal file
101
src/app/components/portfolio/summary/SelectComparisonModal.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Fragment, useState } from "react";
|
||||
|
||||
const SelectComparisonModal = ({
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
scenarios,
|
||||
onAddColumn,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
scenarios: { name: string | null; id: bigint }[];
|
||||
onAddColumn: (columnName: string) => void;
|
||||
}) => {
|
||||
const [selectedScenario, setSelectedScenario] = useState(() => {
|
||||
return scenarios && scenarios.length > 0 ? scenarios[0].id.toString() : "";
|
||||
});
|
||||
|
||||
const addColumn = () => {
|
||||
onAddColumn(selectedScenario);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition show={isOpen} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="fixed inset-0 z-10 overflow-y-auto"
|
||||
onClose={setIsOpen}
|
||||
>
|
||||
<div className="min-h-screen px-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black opacity-30" />
|
||||
</Transition.Child>
|
||||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
<span
|
||||
className="inline-block h-screen align-middle"
|
||||
aria-hidden="true"
|
||||
>
|
||||
​
|
||||
</span>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<div className="inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-900"
|
||||
>
|
||||
Add Comparison
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<select
|
||||
className="w-full p-2 border border-gray-300 rounded-md"
|
||||
value={selectedScenario}
|
||||
onChange={(e) => setSelectedScenario(e.target.value)}
|
||||
>
|
||||
{scenarios.map((scenario) => (
|
||||
<option
|
||||
key={scenario.id.toString()}
|
||||
value={String(scenario.id)}
|
||||
>
|
||||
{scenario.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center px-4 py-2 text-sm font-medium text-blue-900 bg-blue-100 border border-transparent rounded-md hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
|
||||
onClick={addColumn}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectComparisonModal;
|
||||
245
src/app/components/portfolio/summary/SummaryTable.tsx
Normal file
245
src/app/components/portfolio/summary/SummaryTable.tsx
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
useReactTable,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
ColumnDef,
|
||||
} from "@tanstack/react-table";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/app/shadcn_components/ui/table";
|
||||
import SelectComparisonModal from "./SelectComparisonModal";
|
||||
import EpcBarChart from "./EpcBarChart";
|
||||
import { DataItem, ChartData } from "@/app/portfolio/[slug]/utils";
|
||||
import { UseQueryOptions, useQuery } from "@tanstack/react-query";
|
||||
|
||||
export async function getComparsionOverviewScenarioData(
|
||||
scenarioId: string
|
||||
): Promise<Promise<DataItem[]>> {
|
||||
const url = process.env.URL || "" + `/api/portfolio/scenario/${scenarioId}`;
|
||||
|
||||
// This is the client side counterpart to getOverviewScenarioData
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
next: { revalidate: 60 },
|
||||
});
|
||||
if (!response.ok) {
|
||||
// Print the error
|
||||
console.error(response.statusText);
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
interface UseFetchScenarioSummaryProps {
|
||||
scenarioId: string; // Assuming scenarioId can be null to handle cases before it's available
|
||||
onSuccess: (data: DataItem[]) => void;
|
||||
options?: Omit<UseQueryOptions<DataItem[], Error>, "queryKey" | "queryFn">;
|
||||
}
|
||||
|
||||
export const useFetchScenarioSummary = ({
|
||||
scenarioId,
|
||||
onSuccess,
|
||||
}: UseFetchScenarioSummaryProps) => {
|
||||
return useQuery({
|
||||
queryKey: ["scenarioSummary", scenarioId],
|
||||
queryFn: () => getComparsionOverviewScenarioData(scenarioId as string), // Ensure non-null assertion or handle potential null inside the function
|
||||
onSuccess,
|
||||
enabled: scenarioId !== "no-id", // Ensure the query only runs when portfolioId is not null
|
||||
});
|
||||
};
|
||||
|
||||
const shouldHighlightRow = (title: string) => {
|
||||
return [
|
||||
"Potential valuation improvement/unit - highly indicative EPC effect*",
|
||||
"Potential valuation return on investment - highly indicative EPC effect*",
|
||||
].includes(title);
|
||||
};
|
||||
|
||||
const formatCo2Title = (content: any) => {
|
||||
if (content === "CO2/unit") {
|
||||
return "CO₂/unit";
|
||||
}
|
||||
|
||||
if (content === "£ per CO2 reduction") {
|
||||
return "£ per CO₂ reduction";
|
||||
}
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
const SummaryTable = ({
|
||||
data,
|
||||
scenarios,
|
||||
}: {
|
||||
data: DataItem[];
|
||||
scenarios: { name: string | null; id: bigint }[];
|
||||
}) => {
|
||||
// TODO: Right now a user can ad multiple comparisons of the same column, we should prevent this
|
||||
|
||||
const initialColumnSetup = () => {
|
||||
const initialColumns: ColumnDef<DataItem>[] = [
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: () => null,
|
||||
cell: (info) => <b>{info.getValue() as string}</b>,
|
||||
},
|
||||
];
|
||||
|
||||
// Retrieve the scenario names
|
||||
const predefinedScenarios = data[0].scenarios.map(
|
||||
(scenario) => scenario.scenarioName
|
||||
);
|
||||
|
||||
predefinedScenarios.forEach((scenarioName) => {
|
||||
initialColumns.push({
|
||||
id: scenarioName,
|
||||
header: () => <span>{scenarioName}</span>,
|
||||
accessorFn: (row) => {
|
||||
const scenarioData = row.scenarios.find(
|
||||
(s) => s.scenarioName === scenarioName
|
||||
)?.data;
|
||||
return scenarioData;
|
||||
},
|
||||
cell: (info) => {
|
||||
const value = info.getValue();
|
||||
// Render chart if data is array and matches the chart data structure, otherwise render as text
|
||||
return Array.isArray(value) && value[0]?.name ? (
|
||||
<EpcBarChart chartdata={value as ChartData[]} />
|
||||
) : (
|
||||
<span>{value as String}</span>
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return initialColumns;
|
||||
};
|
||||
|
||||
const [columns, setColumns] =
|
||||
useState<ColumnDef<DataItem>[]>(initialColumnSetup);
|
||||
|
||||
const [managedData, setManagedData] = useState<DataItem[]>(data || []);
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const [selectedScenarioId, setSelectedScenarioId] = useState<string>("no-id");
|
||||
|
||||
// Fetch and handle data when a new scenario is selected
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
data: fetchedData,
|
||||
} = useFetchScenarioSummary({
|
||||
scenarioId: selectedScenarioId,
|
||||
onSuccess: (fetchedData: DataItem[]) => {
|
||||
const updatedData = managedData.map((item) => ({
|
||||
...item,
|
||||
scenarios: [
|
||||
...item.scenarios,
|
||||
...(fetchedData.find((fi) => fi.title === item.title)?.scenarios ||
|
||||
[]),
|
||||
],
|
||||
}));
|
||||
setManagedData(updatedData);
|
||||
if (fetchedData[0] && fetchedData[0].scenarios.length > 0) {
|
||||
addColumn(fetchedData[0].scenarios[0].scenarioName);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const table = useReactTable({
|
||||
data: managedData,
|
||||
columns: columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
const addColumn = (scenarioName: string) => {
|
||||
const newColumn: ColumnDef<DataItem> = {
|
||||
id: scenarioName.toLowerCase().replace(/\s+/g, "_"),
|
||||
accessorFn: (row) =>
|
||||
row.scenarios.find((s) => s.scenarioName === scenarioName)?.data,
|
||||
header: () => <span>{scenarioName}</span>,
|
||||
cell: (info) => {
|
||||
const value = info.getValue();
|
||||
return Array.isArray(value) ? (
|
||||
<EpcBarChart chartdata={value as ChartData[]} />
|
||||
) : (
|
||||
<span>{String(value)}</span>
|
||||
);
|
||||
},
|
||||
};
|
||||
setColumns((oldColumns) => [...oldColumns, newColumn]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-8">
|
||||
<button
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
className="mb-4 p-2 bg-brandgold text-white rounded disabled:opacity-50"
|
||||
// If scenarios is empty, we disable this button
|
||||
disabled={scenarios.length === 0}
|
||||
>
|
||||
Add Comparison
|
||||
</button>
|
||||
<SelectComparisonModal
|
||||
isOpen={isModalOpen}
|
||||
setIsOpen={setIsModalOpen}
|
||||
scenarios={scenarios}
|
||||
onAddColumn={(id: string) => {
|
||||
setSelectedScenarioId(id);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="overflow-x-auto shadow-md sm:rounded-lg">
|
||||
<Table className="min-w-full">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id} className="px-6 py-3">
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
style={
|
||||
shouldHighlightRow(row.original.title)
|
||||
? { background: "#e1e1e1" }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id} className="px-6 py-4">
|
||||
{flexRender(cell.column.columnDef.cell, {
|
||||
...cell.getContext(),
|
||||
getValue: () => formatCo2Title(cell.getValue()),
|
||||
})}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SummaryTable;
|
||||
71
src/app/components/signin/CredentialsButton.tsx
Normal file
71
src/app/components/signin/CredentialsButton.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
"use client";
|
||||
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useState, useEffect, SetStateAction } from "react";
|
||||
import { Input } from "@/app/shadcn_components/ui/input";
|
||||
import { Button } from "@/app/shadcn_components/ui/button";
|
||||
import { ChevronRightIcon } from "@heroicons/react/20/solid";
|
||||
|
||||
export default function EmailSignInButton({
|
||||
error: initialError,
|
||||
}: {
|
||||
error: string | undefined;
|
||||
}) {
|
||||
const [email, setEmail] = useState("");
|
||||
const [error, setError] = useState(initialError);
|
||||
|
||||
const handleSubmit = async (e: { preventDefault: () => void }) => {
|
||||
e.preventDefault();
|
||||
const res = await signIn("credentials", {
|
||||
email,
|
||||
});
|
||||
|
||||
if (res?.error) {
|
||||
setError("You are not a valid user.");
|
||||
} else {
|
||||
console.log("Login successful");
|
||||
}
|
||||
};
|
||||
|
||||
const handleEmailChange = (e: {
|
||||
target: { value: SetStateAction<string> };
|
||||
}) => {
|
||||
setEmail(e.target.value);
|
||||
if (error) {
|
||||
setError(undefined); // Clear the error when the user starts typing
|
||||
}
|
||||
};
|
||||
|
||||
// Sync initial error state with server-side error prop
|
||||
useEffect(() => {
|
||||
setError(initialError);
|
||||
}, [initialError]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="w-full">
|
||||
{/* Wrapper to control width and layout */}
|
||||
<div className="flex items-center w-full space-x-1">
|
||||
{/* Email input field using shadcn input */}
|
||||
<Input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={handleEmailChange}
|
||||
placeholder="Enter your email"
|
||||
required
|
||||
className="flex-1 h-10 rounded-lg border-gray-300" // Full width input
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
className="h-10 w-10 bg-brandblue text-white hover:bg-hoverblue rounded-lg flex items-center justify-center" // Fixed size button
|
||||
>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Reserve space for the error message */}
|
||||
<div className="min-h-[3rem] text-center">
|
||||
{error && <p className="text-red-500">You are not a valid user.</p>}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
111
src/app/components/signin/MicrosoftSignInButton.jsx
Normal file
111
src/app/components/signin/MicrosoftSignInButton.jsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
"use client";
|
||||
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { signIn } from "next-auth/react";
|
||||
|
||||
import Button from "./Button";
|
||||
|
||||
const MicrosoftSignInButton = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const callbackUrl = searchParams.get("callbackUrl");
|
||||
|
||||
return (
|
||||
<Button
|
||||
data-testid="microsoft-signin-btn"
|
||||
className="text-black hover:text-gray-400 text-xl hover:bg-gray-100 rounded-lg p-0"
|
||||
onClick={() => signIn("azure-ad-b2c", { callbackUrl })} // Note the provider ID "azure-ad" must match the one you've set in NextAuth config
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 215 41"
|
||||
>
|
||||
<title>MS-SymbolLockup</title>
|
||||
<rect width="100%" height="100%" fill="#2f2f2f" />
|
||||
<path
|
||||
d="M45.812,25.082V23.288a2.77,2.77,0,0,0,.573.4,4.484,4.484,0,0,0,.706.3,5.486,5.486,0,0,0,.745.187,3.954,3.954,0,0,0,.687.065,2.928,2.928,0,0,0,1.634-.365,1.2,1.2,0,0,0,.537-1.062,1.167,1.167,0,0,0-.178-.649,1.939,1.939,0,0,0-.5-.5,5.412,5.412,0,0,0-.757-.435q-.435-.209-.932-.436-.533-.285-.994-.578a4.285,4.285,0,0,1-.8-.648,2.724,2.724,0,0,1-.533-.8,2.6,2.6,0,0,1-.194-1.047,2.416,2.416,0,0,1,.333-1.285,2.794,2.794,0,0,1,.877-.9,4.019,4.019,0,0,1,1.239-.528,5.906,5.906,0,0,1,1.418-.172,5.692,5.692,0,0,1,2.4.374v1.721a3.817,3.817,0,0,0-2.295-.645,4.093,4.093,0,0,0-.771.074,2.335,2.335,0,0,0-.687.241,1.5,1.5,0,0,0-.494.433,1.06,1.06,0,0,0-.189.637,1.221,1.221,0,0,0,.145.608,1.573,1.573,0,0,0,.428.468,4.321,4.321,0,0,0,.688.414c.27.134.584.28.939.436q.548.285,1.034.6a4.881,4.881,0,0,1,.856.7,3.075,3.075,0,0,1,.585.846,2.493,2.493,0,0,1,.215,1.058,2.625,2.625,0,0,1-.322,1.348,2.584,2.584,0,0,1-.866.892,3.786,3.786,0,0,1-1.254.5,6.959,6.959,0,0,1-1.5.155c-.176,0-.392-.014-.647-.04s-.518-.067-.786-.117a7.75,7.75,0,0,1-.76-.187A2.373,2.373,0,0,1,45.812,25.082Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M55.109,16.426a1.021,1.021,0,0,1-.713-.272.891.891,0,0,1-.3-.688.917.917,0,0,1,.3-.7,1.009,1.009,0,0,1,.713-.278,1.041,1.041,0,0,1,.732.278.915.915,0,0,1,.3.7.9.9,0,0,1-.3.678A1.035,1.035,0,0,1,55.109,16.426ZM55.95,25.5h-1.7V18h1.7Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M64.979,24.9q0,4.131-4.146,4.131a6.166,6.166,0,0,1-2.551-.491V26.986a4.712,4.712,0,0,0,2.332.7,2.341,2.341,0,0,0,2.668-2.628V24.24h-.029a2.938,2.938,0,0,1-4.733.436,4.046,4.046,0,0,1-.837-2.684,4.738,4.738,0,0,1,.9-3.04,2.988,2.988,0,0,1,2.471-1.128,2.38,2.38,0,0,1,2.2,1.216h.029V18h1.7ZM63.3,22.064v-.973a1.91,1.91,0,0,0-.523-1.352,1.71,1.71,0,0,0-1.3-.559,1.789,1.789,0,0,0-1.51.714,3.223,3.223,0,0,0-.545,2,2.78,2.78,0,0,0,.523,1.769,1.675,1.675,0,0,0,1.385.662,1.8,1.8,0,0,0,1.426-.632A2.4,2.4,0,0,0,63.3,22.064Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M73.853,25.5h-1.7V21.273q0-2.1-1.483-2.1a1.616,1.616,0,0,0-1.279.582,2.167,2.167,0,0,0-.505,1.469V25.5h-1.7V18h1.7v1.245h.029a2.669,2.669,0,0,1,2.428-1.421,2.257,2.257,0,0,1,1.863.795,3.57,3.57,0,0,1,.644,2.3Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M80.892,16.426a1.017,1.017,0,0,1-.713-.272.889.889,0,0,1-.3-.688.915.915,0,0,1,.3-.7,1,1,0,0,1,.713-.278,1.038,1.038,0,0,1,.731.278.915.915,0,0,1,.3.7.9.9,0,0,1-.3.678A1.033,1.033,0,0,1,80.892,16.426Zm.84,9.074h-1.7V18h1.7Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M90.614,25.5h-1.7V21.273q0-2.1-1.483-2.1a1.62,1.62,0,0,0-1.28.582,2.167,2.167,0,0,0-.5,1.469V25.5h-1.7V18h1.7v1.245h.03a2.668,2.668,0,0,1,2.427-1.421,2.258,2.258,0,0,1,1.864.795,3.576,3.576,0,0,1,.643,2.3Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M106.865,18l-2.208,7.5h-1.776l-1.36-5.083a3.291,3.291,0,0,1-.1-.659h-.029a3.018,3.018,0,0,1-.132.644l-1.477,5.1H98.042l-2.2-7.5H97.6l1.36,5.405a3.308,3.308,0,0,1,.087.645H99.1a3.384,3.384,0,0,1,.117-.659L100.725,18h1.593l1.345,5.428a3.832,3.832,0,0,1,.095.644h.052a3.3,3.3,0,0,1,.109-.644L105.249,18Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M108.977,16.426a1.017,1.017,0,0,1-.713-.272.889.889,0,0,1-.3-.688.915.915,0,0,1,.3-.7,1,1,0,0,1,.713-.278,1.038,1.038,0,0,1,.731.278.915.915,0,0,1,.3.7.9.9,0,0,1-.3.678A1.033,1.033,0,0,1,108.977,16.426Zm.84,9.074h-1.7V18h1.7Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M115.979,25.42a2.944,2.944,0,0,1-1.307.248q-2.18,0-2.179-2.094V19.333h-1.25V18h1.25V16.264l1.7-.483V18h1.79v1.333h-1.79v3.75a1.478,1.478,0,0,0,.242.952,1,1,0,0,0,.8.285,1.16,1.16,0,0,0,.745-.248Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M124.094,25.5h-1.7V21.4q0-2.226-1.483-2.226a1.555,1.555,0,0,0-1.258.644,2.573,2.573,0,0,0-.511,1.649V25.5h-1.7V14.4h1.7v4.849h.029a2.679,2.679,0,0,1,2.428-1.421q2.492,0,2.492,3.055Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M141.719,25.5h-1.726V18.7q0-.835.1-2.043h-.03a6.992,6.992,0,0,1-.285.988L136.652,25.5h-1.2l-3.136-7.793a7.371,7.371,0,0,1-.277-1.047h-.029q.059.63.058,2.058V25.5h-1.608V15h2.449l2.756,7a10.415,10.415,0,0,1,.409,1.2h.036c.181-.551.327-.962.439-1.23L139.357,15h2.362Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M144.964,16.426a1.019,1.019,0,0,1-.713-.272.892.892,0,0,1-.3-.688.918.918,0,0,1,.3-.7,1.007,1.007,0,0,1,.713-.278,1.038,1.038,0,0,1,.731.278.911.911,0,0,1,.3.7.9.9,0,0,1-.3.678A1.033,1.033,0,0,1,144.964,16.426Zm.841,9.074h-1.7V18h1.7Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M153.378,25.156a4.185,4.185,0,0,1-2.127.52,3.6,3.6,0,0,1-2.69-1.044,3.7,3.7,0,0,1-1.024-2.706,4.074,4.074,0,0,1,1.1-2.978,3.93,3.93,0,0,1,2.942-1.124,4.281,4.281,0,0,1,1.806.36v1.582a2.73,2.73,0,0,0-1.667-.586,2.312,2.312,0,0,0-1.762.728,2.669,2.669,0,0,0-.687,1.908,2.54,2.54,0,0,0,.647,1.838,2.291,2.291,0,0,0,1.736.674,2.708,2.708,0,0,0,1.725-.652Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M159.4,19.619a1.4,1.4,0,0,0-.884-.242,1.514,1.514,0,0,0-1.258.682,3.047,3.047,0,0,0-.5,1.852V25.5h-1.7V18h1.7v1.545h.029a2.6,2.6,0,0,1,.764-1.233,1.72,1.72,0,0,1,1.151-.444,1.425,1.425,0,0,1,.7.14Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M163.788,25.676a3.71,3.71,0,0,1-2.767-1.051,3.8,3.8,0,0,1-1.035-2.787,3.7,3.7,0,0,1,3.985-4.014,3.581,3.581,0,0,1,2.733,1.033,3.994,3.994,0,0,1,.98,2.864,3.938,3.938,0,0,1-1.056,2.875A3.8,3.8,0,0,1,163.788,25.676Zm.08-6.5a1.932,1.932,0,0,0-1.571.7,2.913,2.913,0,0,0-.578,1.919,2.744,2.744,0,0,0,.585,1.856,1.957,1.957,0,0,0,1.564.678,1.862,1.862,0,0,0,1.539-.666,2.95,2.95,0,0,0,.537-1.9,2.99,2.99,0,0,0-.537-1.911A1.851,1.851,0,0,0,163.868,19.18Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M168.94,25.266V23.691a3.383,3.383,0,0,0,2.1.725q1.535,0,1.535-.908a.714.714,0,0,0-.132-.436,1.263,1.263,0,0,0-.354-.318,2.864,2.864,0,0,0-.526-.25c-.2-.072-.428-.155-.677-.248a7.074,7.074,0,0,1-.829-.389,2.526,2.526,0,0,1-.615-.465,1.758,1.758,0,0,1-.369-.59,2.168,2.168,0,0,1-.124-.769,1.775,1.775,0,0,1,.256-.955,2.224,2.224,0,0,1,.687-.7,3.294,3.294,0,0,1,.979-.425A4.49,4.49,0,0,1,172,17.824a5.163,5.163,0,0,1,1.856.315v1.487a3.127,3.127,0,0,0-1.812-.542,2.323,2.323,0,0,0-.582.066,1.477,1.477,0,0,0-.442.183.893.893,0,0,0-.285.282.677.677,0,0,0-.1.363.779.779,0,0,0,.1.41.936.936,0,0,0,.3.3,2.675,2.675,0,0,0,.482.234q.282.105.648.23a9.5,9.5,0,0,1,.866.4,2.872,2.872,0,0,1,.654.465,1.789,1.789,0,0,1,.416.6,2.034,2.034,0,0,1,.147.81,1.855,1.855,0,0,1-.263,1,2.212,2.212,0,0,1-.7.7,3.28,3.28,0,0,1-1.013.413,5.2,5.2,0,0,1-1.209.136A5.1,5.1,0,0,1,168.94,25.266Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M179.183,25.676a3.711,3.711,0,0,1-2.768-1.051,3.8,3.8,0,0,1-1.034-2.787,3.7,3.7,0,0,1,3.984-4.014,3.585,3.585,0,0,1,2.734,1.033,3.993,3.993,0,0,1,.979,2.864,3.934,3.934,0,0,1-1.056,2.875A3.794,3.794,0,0,1,179.183,25.676Zm.08-6.5a1.934,1.934,0,0,0-1.572.7,2.919,2.919,0,0,0-.578,1.919,2.749,2.749,0,0,0,.585,1.856,1.959,1.959,0,0,0,1.565.678,1.864,1.864,0,0,0,1.539-.666,2.956,2.956,0,0,0,.537-1.9,3,3,0,0,0-.537-1.911A1.852,1.852,0,0,0,179.263,19.18Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M188.787,15.781a1.523,1.523,0,0,0-.782-.2q-1.235,0-1.235,1.4V18h1.74v1.333h-1.733V25.5h-1.7V19.333H183.8V18h1.279V16.784a2.37,2.37,0,0,1,.775-1.871,2.817,2.817,0,0,1,1.937-.684,2.866,2.866,0,0,1,.994.138Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M193.94,25.42a2.944,2.944,0,0,1-1.307.248q-2.179,0-2.179-2.094V19.333H189.2V18h1.25V16.264l1.7-.483V18h1.79v1.333h-1.79v3.75a1.472,1.472,0,0,0,.242.952,1,1,0,0,0,.8.285,1.162,1.162,0,0,0,.745-.248Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<rect x="13" y="11" width="9" height="9" fill="#f25022" />
|
||||
<rect x="13" y="21" width="9" height="9" fill="#00a4ef" />
|
||||
<rect x="23" y="11" width="9" height="9" fill="#7fba00" />
|
||||
<rect x="23" y="21" width="9" height="9" fill="#ffb900" />
|
||||
</svg>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default MicrosoftSignInButton;
|
||||
|
|
@ -5,6 +5,8 @@ import * as portfolioSchema from "@/app/db/schema/portfolio";
|
|||
import * as propertySchema from "@/app/db/schema/property";
|
||||
import * as recommendationSchema from "@/app/db/schema/recommendations";
|
||||
import * as materialSchema from "@/app/db/schema/materials";
|
||||
import * as solarSchema from "@/app/db/schema/solar";
|
||||
import * as EnergyAssessmentsSchema from "@/app/db/schema/energy_assessments";
|
||||
import * as Relations from "@/app/db/schema/relations";
|
||||
|
||||
export const pool = new Pool({
|
||||
|
|
@ -22,7 +24,9 @@ const schema = {
|
|||
...propertySchema,
|
||||
...recommendationSchema,
|
||||
...materialSchema,
|
||||
...solarSchema,
|
||||
...Relations,
|
||||
...EnergyAssessmentsSchema,
|
||||
};
|
||||
|
||||
export const db = drizzle(pool, {
|
||||
|
|
|
|||
1
src/app/db/migrations/0061_little_doctor_faustus.sql
Normal file
1
src/app/db/migrations/0061_little_doctor_faustus.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "property" ADD COLUMN "current_valuation" real;
|
||||
3
src/app/db/migrations/0062_cute_zodiak.sql
Normal file
3
src/app/db/migrations/0062_cute_zodiak.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE "plan" ADD COLUMN "valuation_increase_lower_bound" real;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "valuation_increase_upper_bound" real;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "valuation_increase_average" real;
|
||||
20
src/app/db/migrations/0063_elite_black_tarantula.sql
Normal file
20
src/app/db/migrations/0063_elite_black_tarantula.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE IF NOT EXISTS "non_intrusive_survey" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"uprn" bigint,
|
||||
"survey_date" timestamp,
|
||||
"surveyor" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "non_intrusive_survey_notes" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"survey_id" bigint NOT NULL,
|
||||
"title" text,
|
||||
"note" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "recommendation" ADD COLUMN "is_override" boolean DEFAULT false;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "non_intrusive_survey_notes" ADD CONSTRAINT "non_intrusive_survey_notes_survey_id_non_intrusive_survey_id_fk" FOREIGN KEY ("survey_id") REFERENCES "non_intrusive_survey"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
3
src/app/db/migrations/0064_icy_blue_shield.sql
Normal file
3
src/app/db/migrations/0064_icy_blue_shield.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE "non_intrusive_survey" ALTER COLUMN "uprn" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "non_intrusive_survey" ALTER COLUMN "survey_date" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "non_intrusive_survey" ALTER COLUMN "surveyor" SET NOT NULL;
|
||||
2
src/app/db/migrations/0065_watery_korvac.sql
Normal file
2
src/app/db/migrations/0065_watery_korvac.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "non_intrusive_survey_notes" ALTER COLUMN "title" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "non_intrusive_survey_notes" ALTER COLUMN "note" SET NOT NULL;
|
||||
2
src/app/db/migrations/0066_eager_cerise.sql
Normal file
2
src/app/db/migrations/0066_eager_cerise.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "recommendation" ADD COLUMN "already_installed" boolean DEFAULT false;--> statement-breakpoint
|
||||
ALTER TABLE "recommendation" DROP COLUMN IF EXISTS "is_override";
|
||||
13
src/app/db/migrations/0067_previous_sasquatch.sql
Normal file
13
src/app/db/migrations/0067_previous_sasquatch.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
ALTER TABLE "portfolio" ADD COLUMN "epc_breakdown_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "epc_breakdown_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "n_units_to_retrofit" integer;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "co2_per_unit_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "co2_per_unit_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "energy_bill_per_unit_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "energy_bill_per_unit_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "energy_consumption_per_unit_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "energy_consumption_per_unit_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "valuation_improvement_per_unit" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "cost_per_unit" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "cost_per_co2_saved" text;--> statement-breakpoint
|
||||
ALTER TABLE "portfolio" ADD COLUMN "cost_per_sap_point" text;
|
||||
1
src/app/db/migrations/0068_demonic_roughhouse.sql
Normal file
1
src/app/db/migrations/0068_demonic_roughhouse.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "portfolio" ADD COLUMN "valuation_return_on_investment" text;
|
||||
1
src/app/db/migrations/0069_rich_klaw.sql
Normal file
1
src/app/db/migrations/0069_rich_klaw.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "material" ADD COLUMN "is_installer_quote" boolean DEFAULT false;
|
||||
1
src/app/db/migrations/0070_sweet_riptide.sql
Normal file
1
src/app/db/migrations/0070_sweet_riptide.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "recommendation" RENAME COLUMN "adjusted_heat_demand" TO "kwh_savings";
|
||||
9
src/app/db/migrations/0071_same_puppet_master.sql
Normal file
9
src/app/db/migrations/0071_same_puppet_master.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE IF NOT EXISTS "solar" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"longitude" real NOT NULL,
|
||||
"latitude" real NOT NULL,
|
||||
"uprn" text NOT NULL,
|
||||
"created_at" timestamp (6) with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp (6) with time zone DEFAULT now() NOT NULL,
|
||||
"api_response" jsonb NOT NULL
|
||||
);
|
||||
1
src/app/db/migrations/0072_plain_vapor.sql
Normal file
1
src/app/db/migrations/0072_plain_vapor.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "solar" RENAME COLUMN "api_response" TO "google_api_response";
|
||||
1
src/app/db/migrations/0073_youthful_havok.sql
Normal file
1
src/app/db/migrations/0073_youthful_havok.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "solar" ALTER COLUMN "uprn" SET DATA TYPE bigint USING "uprn"::bigint;
|
||||
27
src/app/db/migrations/0074_regular_blonde_phantom.sql
Normal file
27
src/app/db/migrations/0074_regular_blonde_phantom.sql
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
DO $$ BEGIN
|
||||
CREATE TYPE "scenario_type" AS ENUM('unit', 'building');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "solar_scenario" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"solar_id" bigint NOT NULL,
|
||||
"scenario_type" "scenario_type" NOT NULL,
|
||||
"number_panels" integer NOT NULL,
|
||||
"array_kwhp" integer NOT NULL,
|
||||
"lifetime_dc_kwh" real NOT NULL,
|
||||
"yearly_dc_kwh" real NOT NULL,
|
||||
"lifetime_ac_kwh" real,
|
||||
"yearly_ac_kwh" real,
|
||||
"cost" real NOT NULL,
|
||||
"expected_payback_years" real,
|
||||
"panelled_roof_area" real NOT NULL,
|
||||
"is_default" boolean NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "solar_scenario" ADD CONSTRAINT "solar_scenario_solar_id_solar_id_fk" FOREIGN KEY ("solar_id") REFERENCES "solar"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
115
src/app/db/migrations/0075_even_phalanx.sql
Normal file
115
src/app/db/migrations/0075_even_phalanx.sql
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
CREATE TABLE IF NOT EXISTS "energy_assessments" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"uprn" bigint NOT NULL,
|
||||
"uprn_source" text NOT NULL,
|
||||
"property_type" text NOT NULL,
|
||||
"building_reference_number" text,
|
||||
"current_energy_efficiency" text NOT NULL,
|
||||
"current_energy_rating" text NOT NULL,
|
||||
"address1" text NOT NULL,
|
||||
"address2" text NOT NULL,
|
||||
"address3" text,
|
||||
"posttown" text NOT NULL,
|
||||
"postcode" text NOT NULL,
|
||||
"address" text NOT NULL,
|
||||
"county" text[],
|
||||
"constituency" text,
|
||||
"constituency_label" text,
|
||||
"low_energy_fixed_light_count" text NOT NULL,
|
||||
"construction_age_band" text NOT NULL,
|
||||
"mainheat_energy_eff" text NOT NULL,
|
||||
"windows_env_eff" text NOT NULL,
|
||||
"lighting_energy_eff" text NOT NULL,
|
||||
"environmentImpactPotential" text NOT NULL,
|
||||
"mainheatcont_description" text NOT NULL,
|
||||
"sheating_energy_eff" text NOT NULL,
|
||||
"local_authority" text NOT NULL,
|
||||
"local_authority_label" text NOT NULL,
|
||||
"fixed_lighting_outlets_count" text NOT NULL,
|
||||
"energy_tariff" text NOT NULL,
|
||||
"mechanical_ventilation" text NOT NULL,
|
||||
"solar_water_heating_flag" text NOT NULL,
|
||||
"co2_emissions_potential" text NOT NULL,
|
||||
"number_heated_rooms" text NOT NULL,
|
||||
"floor_description" text NOT NULL,
|
||||
"energy_consumption_potential" text NOT NULL,
|
||||
"built_form" text NOT NULL,
|
||||
"number_open_fireplaces" text NOT NULL,
|
||||
"windows_description" text NOT NULL,
|
||||
"glazed_area" text NOT NULL,
|
||||
"inspection_date" timestamp (6) with time zone NOT NULL,
|
||||
"mains_gas_flag" text NOT NULL,
|
||||
"co2_emiss_curr_per_floor_area" text NOT NULL,
|
||||
"heat_loss_corridor" text NOT NULL,
|
||||
"unheated_corridor_length" text,
|
||||
"flat_storey_count" text,
|
||||
"roof_energy_eff" text NOT NULL,
|
||||
"total_floor_area" text NOT NULL,
|
||||
"environment_impact_current" text NOT NULL,
|
||||
"roof_description" text NOT NULL,
|
||||
"floor_energy_eff" text NOT NULL,
|
||||
"number_habitable_rooms" text NOT NULL,
|
||||
"hot_water_env_eff" text NOT NULL,
|
||||
"mainheatc_energy_eff" text NOT NULL,
|
||||
"main_fuel" text NOT NULL,
|
||||
"lighting_env_eff" text NOT NULL,
|
||||
"windows_energy_eff" text NOT NULL,
|
||||
"floor_env_eff" text NOT NULL,
|
||||
"sheating_env_eff" text NOT NULL,
|
||||
"lighting_description" text NOT NULL,
|
||||
"roof_env_eff" text NOT NULL,
|
||||
"walls_energy_eff" text NOT NULL,
|
||||
"photo_supply" text NOT NULL,
|
||||
"lighting_cost_potential" text NOT NULL,
|
||||
"mainheat_env_eff" text NOT NULL,
|
||||
"multi_glaze_proportion" text NOT NULL,
|
||||
"main_heating_controls" text NOT NULL,
|
||||
"flat_top_storey" text,
|
||||
"secondheat_description" text NOT NULL,
|
||||
"walls_env_eff" text NOT NULL,
|
||||
"transaction_type" text NOT NULL,
|
||||
"extension_count" text NOT NULL,
|
||||
"mainheatc_env_eff" text NOT NULL,
|
||||
"lmk_key" text,
|
||||
"wind_turbine_count" text NOT NULL,
|
||||
"tenure" text NOT NULL,
|
||||
"floor_level" text NOT NULL,
|
||||
"potential_energy_efficiency" text NOT NULL,
|
||||
"potential_energy_rating" text NOT NULL,
|
||||
"hot_water_energy_eff" text NOT NULL,
|
||||
"low_energy_lighting" text NOT NULL,
|
||||
"walls_description" text NOT NULL,
|
||||
"hotwater_description" text NOT NULL,
|
||||
"co2_emissions_current" text NOT NULL,
|
||||
"heating_cost_current" text NOT NULL,
|
||||
"heating_cost_potential" text NOT NULL,
|
||||
"hot_water_cost_current" text NOT NULL,
|
||||
"hot_water_cost_potential" text NOT NULL,
|
||||
"lighting_cost_current" text NOT NULL,
|
||||
"energy_consumption_current" text NOT NULL,
|
||||
"lodgement_date" timestamp (6) with time zone NOT NULL,
|
||||
"lodgement_datetime" timestamp (6) with time zone NOT NULL,
|
||||
"mainheat_description" text NOT NULL,
|
||||
"floor_height" real NOT NULL,
|
||||
"glazed_type" text NOT NULL,
|
||||
"file_location" text NOT NULL,
|
||||
"surveyor_name" text NOT NULL,
|
||||
"surveyor_company" text NOT NULL,
|
||||
"space_heating_kwh" text NOT NULL,
|
||||
"water_heating_kwh" text NOT NULL,
|
||||
"number_of_doors" integer NOT NULL,
|
||||
"number_of_insulated_doors" integer NOT NULL,
|
||||
"number_of_floors" integer NOT NULL,
|
||||
"insulation_wall_area" real NOT NULL,
|
||||
"heat_loss_perimeter" real NOT NULL,
|
||||
"party_wall_length" real NOT NULL,
|
||||
"perimeter" real NOT NULL,
|
||||
"rooms_with_bath_and_or_shower" integer,
|
||||
"rooms_with_mixer_shower_no_bath" integer,
|
||||
"room_with_bath_and_mixer_shower" integer,
|
||||
"percent_draftproofed" integer,
|
||||
"has_hot_water_cylinder" boolean,
|
||||
"cylinder_insulation_type" text,
|
||||
"cylinder_insulation_thickness" integer,
|
||||
"cylinder_thermostat" boolean
|
||||
);
|
||||
1
src/app/db/migrations/0076_steep_whirlwind.sql
Normal file
1
src/app/db/migrations/0076_steep_whirlwind.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "energy_assessments" RENAME COLUMN "environmentImpactPotential" TO "environment_impact_potential";
|
||||
1
src/app/db/migrations/0077_ambitious_salo.sql
Normal file
1
src/app/db/migrations/0077_ambitious_salo.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "energy_assessments" ALTER COLUMN "county" SET DATA TYPE text;
|
||||
1
src/app/db/migrations/0078_chubby_marrow.sql
Normal file
1
src/app/db/migrations/0078_chubby_marrow.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "energy_assessments" ADD COLUMN "main_dwelling_ground_floor_area" real;
|
||||
2
src/app/db/migrations/0079_chemical_power_pack.sql
Normal file
2
src/app/db/migrations/0079_chemical_power_pack.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "energy_assessments" ADD COLUMN "number_of_windows" integer;--> statement-breakpoint
|
||||
ALTER TABLE "energy_assessments" ADD COLUMN "windows_area" real;
|
||||
2
src/app/db/migrations/0080_glossy_avengers.sql
Normal file
2
src/app/db/migrations/0080_glossy_avengers.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "energy_assessments" ALTER COLUMN "lodgement_date" SET DATA TYPE date;--> statement-breakpoint
|
||||
ALTER TABLE "energy_assessments" ALTER COLUMN "lodgement_datetime" SET DATA TYPE timestamp (6);
|
||||
1
src/app/db/migrations/0081_nebulous_ezekiel.sql
Normal file
1
src/app/db/migrations/0081_nebulous_ezekiel.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "plan" ADD COLUMN "name" text;
|
||||
20
src/app/db/migrations/0082_strong_gateway.sql
Normal file
20
src/app/db/migrations/0082_strong_gateway.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
ALTER TABLE "plan" ADD COLUMN "cost" real;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "total_work_hours" real;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "energy_savings" real;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "co2_equivalent_savings" real;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "energy_cost_savings" real;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "epc_breakdown_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "epc_breakdown_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "number_of_properties" integer;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "n_units_to_retrofit" integer;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "co2_per_unit_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "co2_per_unit_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "energy_bill_per_unit_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "energy_bill_per_unit_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "energy_consumption_per_unit_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "energy_consumption_per_unit_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "valuation_improvement_per_unit" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "cost_per_unit" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "cost_per_co2_saved" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "cost_per_sap_point" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "valuation_return_on_investment" text;
|
||||
34
src/app/db/migrations/0083_harsh_eternals.sql
Normal file
34
src/app/db/migrations/0083_harsh_eternals.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
DO $$ BEGIN
|
||||
CREATE TYPE "housing_type" AS ENUM('Private', 'Social');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "scenario" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"name" text,
|
||||
"budget" real,
|
||||
"portfolio_id" bigint NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"housing_type" "housing_type" NOT NULL,
|
||||
"goal" "goal" NOT NULL,
|
||||
"trigger_file_path" text,
|
||||
"already_installed_file_path" text,
|
||||
"patches_file_path" text,
|
||||
"non_invasive_recommendations_file_path" text,
|
||||
"exclusions" text,
|
||||
"multi_plan" boolean
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "scenario_id" bigint;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "plan" ADD CONSTRAINT "plan_scenario_id_scenario_id_fk" FOREIGN KEY ("scenario_id") REFERENCES "scenario"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "scenario" ADD CONSTRAINT "scenario_portfolio_id_portfolio_id_fk" FOREIGN KEY ("portfolio_id") REFERENCES "portfolio"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
20
src/app/db/migrations/0084_brief_wolf_cub.sql
Normal file
20
src/app/db/migrations/0084_brief_wolf_cub.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "cost";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "total_work_hours";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "energy_savings";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "co2_equivalent_savings";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "energy_cost_savings";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "epc_breakdown_pre_retrofit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "epc_breakdown_post_retrofit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "number_of_properties";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "n_units_to_retrofit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "co2_per_unit_pre_retrofit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "co2_per_unit_post_retrofit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "energy_bill_per_unit_pre_retrofit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "energy_bill_per_unit_post_retrofit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "energy_consumption_per_unit_pre_retrofit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "energy_consumption_per_unit_post_retrofit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "valuation_improvement_per_unit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "cost_per_unit";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "cost_per_co2_saved";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "cost_per_sap_point";--> statement-breakpoint
|
||||
ALTER TABLE "plan" DROP COLUMN IF EXISTS "valuation_return_on_investment";
|
||||
20
src/app/db/migrations/0085_nosy_gideon.sql
Normal file
20
src/app/db/migrations/0085_nosy_gideon.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
ALTER TABLE "scenario" ADD COLUMN "cost" real;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "total_work_hours" real;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "energy_savings" real;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "co2_equivalent_savings" real;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "energy_cost_savings" real;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "epc_breakdown_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "epc_breakdown_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "number_of_properties" integer;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "n_units_to_retrofit" integer;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "co2_per_unit_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "co2_per_unit_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "energy_bill_per_unit_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "energy_bill_per_unit_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "energy_consumption_per_unit_pre_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "energy_consumption_per_unit_post_retrofit" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "valuation_improvement_per_unit" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "cost_per_unit" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "cost_per_co2_saved" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "cost_per_sap_point" text;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "valuation_return_on_investment" text;
|
||||
1
src/app/db/migrations/0086_happy_excalibur.sql
Normal file
1
src/app/db/migrations/0086_happy_excalibur.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "scenario" ADD COLUMN "is_default" boolean;
|
||||
1
src/app/db/migrations/0087_nebulous_umar.sql
Normal file
1
src/app/db/migrations/0087_nebulous_umar.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "scenario" ALTER COLUMN "is_default" SET NOT NULL;
|
||||
2
src/app/db/migrations/0088_many_tana_nile.sql
Normal file
2
src/app/db/migrations/0088_many_tana_nile.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "scenario" ADD COLUMN "property_valuation_increase" real;--> statement-breakpoint
|
||||
ALTER TABLE "scenario" ADD COLUMN "labour_days" real;
|
||||
1
src/app/db/migrations/0089_superb_johnny_blaze.sql
Normal file
1
src/app/db/migrations/0089_superb_johnny_blaze.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "property_details_epc" RENAME COLUMN "adjusted_energy_consumption" TO "current_energy_demand";
|
||||
1
src/app/db/migrations/0090_youthful_silver_centurion.sql
Normal file
1
src/app/db/migrations/0090_youthful_silver_centurion.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "property_details_epc" ADD COLUMN "current_energy_demand_heating_hotwater" real;
|
||||
14
src/app/db/migrations/0091_fuzzy_doctor_spectrum.sql
Normal file
14
src/app/db/migrations/0091_fuzzy_doctor_spectrum.sql
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
DO $$ BEGIN
|
||||
CREATE TYPE "document_type" AS ENUM('EPR', 'Condition Report', 'Evidence Report', 'Summary Information', 'Floor Plan', 'Scenario EPR');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "energy_assessment_documents" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"uprn" bigint NOT NULL,
|
||||
"energy_assessment_id" bigint NOT NULL,
|
||||
"document_type" "document_type" NOT NULL,
|
||||
"document_location" text NOT NULL,
|
||||
"uploaded_at" timestamp (6) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
1
src/app/db/migrations/0092_illegal_magma.sql
Normal file
1
src/app/db/migrations/0092_illegal_magma.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TYPE "document_type" ADD VALUE 'Scenario Draft EPC';
|
||||
1
src/app/db/migrations/0093_blushing_dark_beast.sql
Normal file
1
src/app/db/migrations/0093_blushing_dark_beast.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TYPE "document_type" ADD VALUE 'Scenario Site Notes';
|
||||
24
src/app/db/migrations/0094_old_forge.sql
Normal file
24
src/app/db/migrations/0094_old_forge.sql
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
CREATE TABLE IF NOT EXISTS "energy_assessment_scenarios" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"scenario_name" text NOT NULL,
|
||||
"energy_assessment_id" bigint NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "energy_assessment_documents" ADD COLUMN "scenario_id" bigint;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "energy_assessment_documents" ADD CONSTRAINT "energy_assessment_documents_energy_assessment_id_energy_assessments_id_fk" FOREIGN KEY ("energy_assessment_id") REFERENCES "energy_assessments"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "energy_assessment_documents" ADD CONSTRAINT "energy_assessment_documents_scenario_id_energy_assessments_id_fk" FOREIGN KEY ("scenario_id") REFERENCES "energy_assessments"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "energy_assessment_scenarios" ADD CONSTRAINT "energy_assessment_scenarios_energy_assessment_id_energy_assessments_id_fk" FOREIGN KEY ("energy_assessment_id") REFERENCES "energy_assessments"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
7
src/app/db/migrations/0095_sloppy_ikaris.sql
Normal file
7
src/app/db/migrations/0095_sloppy_ikaris.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
ALTER TABLE "energy_assessment_documents" DROP CONSTRAINT "energy_assessment_documents_scenario_id_energy_assessments_id_fk";
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "energy_assessment_documents" ADD CONSTRAINT "energy_assessment_documents_scenario_id_energy_assessment_scenarios_id_fk" FOREIGN KEY ("scenario_id") REFERENCES "energy_assessment_scenarios"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
1
src/app/db/migrations/0096_married_umar.sql
Normal file
1
src/app/db/migrations/0096_married_umar.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TYPE "status" ADD VALUE 'survey';
|
||||
1466
src/app/db/migrations/meta/0061_snapshot.json
Normal file
1466
src/app/db/migrations/meta/0061_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1484
src/app/db/migrations/meta/0062_snapshot.json
Normal file
1484
src/app/db/migrations/meta/0062_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1571
src/app/db/migrations/meta/0063_snapshot.json
Normal file
1571
src/app/db/migrations/meta/0063_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1571
src/app/db/migrations/meta/0064_snapshot.json
Normal file
1571
src/app/db/migrations/meta/0064_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1571
src/app/db/migrations/meta/0065_snapshot.json
Normal file
1571
src/app/db/migrations/meta/0065_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1571
src/app/db/migrations/meta/0066_snapshot.json
Normal file
1571
src/app/db/migrations/meta/0066_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1649
src/app/db/migrations/meta/0067_snapshot.json
Normal file
1649
src/app/db/migrations/meta/0067_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1655
src/app/db/migrations/meta/0068_snapshot.json
Normal file
1655
src/app/db/migrations/meta/0068_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1662
src/app/db/migrations/meta/0069_snapshot.json
Normal file
1662
src/app/db/migrations/meta/0069_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1664
src/app/db/migrations/meta/0070_snapshot.json
Normal file
1664
src/app/db/migrations/meta/0070_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1715
src/app/db/migrations/meta/0071_snapshot.json
Normal file
1715
src/app/db/migrations/meta/0071_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1717
src/app/db/migrations/meta/0072_snapshot.json
Normal file
1717
src/app/db/migrations/meta/0072_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1715
src/app/db/migrations/meta/0073_snapshot.json
Normal file
1715
src/app/db/migrations/meta/0073_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1823
src/app/db/migrations/meta/0074_snapshot.json
Normal file
1823
src/app/db/migrations/meta/0074_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2510
src/app/db/migrations/meta/0075_snapshot.json
Normal file
2510
src/app/db/migrations/meta/0075_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2512
src/app/db/migrations/meta/0076_snapshot.json
Normal file
2512
src/app/db/migrations/meta/0076_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2510
src/app/db/migrations/meta/0077_snapshot.json
Normal file
2510
src/app/db/migrations/meta/0077_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2516
src/app/db/migrations/meta/0078_snapshot.json
Normal file
2516
src/app/db/migrations/meta/0078_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2528
src/app/db/migrations/meta/0079_snapshot.json
Normal file
2528
src/app/db/migrations/meta/0079_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2528
src/app/db/migrations/meta/0080_snapshot.json
Normal file
2528
src/app/db/migrations/meta/0080_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2534
src/app/db/migrations/meta/0081_snapshot.json
Normal file
2534
src/app/db/migrations/meta/0081_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2654
src/app/db/migrations/meta/0082_snapshot.json
Normal file
2654
src/app/db/migrations/meta/0082_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2782
src/app/db/migrations/meta/0083_snapshot.json
Normal file
2782
src/app/db/migrations/meta/0083_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2662
src/app/db/migrations/meta/0084_snapshot.json
Normal file
2662
src/app/db/migrations/meta/0084_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2782
src/app/db/migrations/meta/0085_snapshot.json
Normal file
2782
src/app/db/migrations/meta/0085_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2788
src/app/db/migrations/meta/0086_snapshot.json
Normal file
2788
src/app/db/migrations/meta/0086_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2788
src/app/db/migrations/meta/0087_snapshot.json
Normal file
2788
src/app/db/migrations/meta/0087_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2800
src/app/db/migrations/meta/0088_snapshot.json
Normal file
2800
src/app/db/migrations/meta/0088_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2802
src/app/db/migrations/meta/0089_snapshot.json
Normal file
2802
src/app/db/migrations/meta/0089_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2806
src/app/db/migrations/meta/0090_snapshot.json
Normal file
2806
src/app/db/migrations/meta/0090_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2863
src/app/db/migrations/meta/0091_snapshot.json
Normal file
2863
src/app/db/migrations/meta/0091_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2863
src/app/db/migrations/meta/0092_snapshot.json
Normal file
2863
src/app/db/migrations/meta/0092_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2864
src/app/db/migrations/meta/0093_snapshot.json
Normal file
2864
src/app/db/migrations/meta/0093_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2938
src/app/db/migrations/meta/0094_snapshot.json
Normal file
2938
src/app/db/migrations/meta/0094_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2938
src/app/db/migrations/meta/0095_snapshot.json
Normal file
2938
src/app/db/migrations/meta/0095_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
2939
src/app/db/migrations/meta/0096_snapshot.json
Normal file
2939
src/app/db/migrations/meta/0096_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue