Convert success list/view

Also adjusted webpack chunks, added tooling configuration
This commit is contained in:
Daniel J. Summers 2021-07-29 23:23:36 -04:00
parent 8cf54d73ca
commit f132970535
21 changed files with 487 additions and 233 deletions

View File

@ -189,6 +189,7 @@ let withReconn (conn : IConnection) =
| false -> ()))
open JobsJobsJobs.Domain.SharedTypes
open RethinkDb.Driver.Ast
/// Profile data access functions
[<RequireQualifiedAccess>]
@ -432,7 +433,11 @@ module Success =
// Retrieve all success stories
let all conn =
// TODO: identify query and fields that will make StoryEntry meaningful
withReconn(conn).ExecuteAsync(fun () ->
r.Table(Table.Success)
.EqJoin("citizenId", r.Table(Table.Citizen))
.Without(r.HashMap("right", "id"))
.Zip()
.Merge(Javascript "function (s) { return { citizenName: s.realName || s.displayName || s.naUser } }")
.Pluck("id", "citizenId", "citizenName", "recordedOn", "fromHere", "hasStory")
.RunResultAsync<StoryEntry list> conn)

View File

@ -2021,6 +2021,122 @@
"tslint": "^5.20.1",
"webpack": "^4.0.0",
"yorkie": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"dev": true,
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"optional": true,
"requires": {
"yallist": "^4.0.0"
}
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"optional": true
}
}
},
"@vue/cli-plugin-vuex": {
@ -2099,6 +2215,44 @@
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
@ -2110,6 +2264,13 @@
"universalify": "^0.1.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
@ -2119,6 +2280,18 @@
"graceful-fs": "^4.1.6"
}
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
@ -2128,11 +2301,33 @@
"minipass": "^3.1.1"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.3.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.3.3.tgz",
"integrity": "sha512-/1GzCuQ6MRORbC+leKTKoTGtpQt60bYe0gDGEextSteA2OM+v201FPha5jzmjQzVhRcwieZeUvezAtG5a/e5cw==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
}
}
}
},
@ -6506,122 +6701,6 @@
}
}
},
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"dev": true,
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"optional": true,
"requires": {
"yallist": "^4.0.0"
}
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"optional": true
}
}
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
@ -12883,87 +12962,6 @@
}
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.3.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.3.3.tgz",
"integrity": "sha512-/1GzCuQ6MRORbC+leKTKoTGtpQt60bYe0gDGEextSteA2OM+v201FPha5jzmjQzVhRcwieZeUvezAtG5a/e5cw==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"vue-router": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.10.tgz",

View File

@ -1,5 +1,5 @@
import { MarkedOptions } from 'marked'
import { Citizen, Continent, Count, LogOnSuccess, Profile, ProfileForView } from './types'
import { Citizen, Continent, Count, LogOnSuccess, Profile, ProfileForView, StoryEntry, Success } from './types'
/**
* Create a URL that will access the API
@ -149,6 +149,29 @@ export default {
*/
delete: async (user : LogOnSuccess) : Promise<string | undefined> =>
apiAction(await fetch(apiUrl('profile'), reqInit('DELETE', user)), 'deleting profile')
},
/** API functions for success stories */
success: {
/**
* Retrieve all success stories
*
* @param user The currently logged-on user
* @returns All success stories (if any exist), undefined (if none exist), or an error
*/
list: async (user : LogOnSuccess) : Promise<StoryEntry[] | string | undefined> =>
apiResult<StoryEntry[]>(await fetch(apiUrl('success/list'), reqInit('GET', user)), 'retrieving success stories'),
/**
* Retrieve a success story by its ID
*
* @param id The success story ID to be retrieved
* @param user The currently logged-on user
* @returns The success story, or an error
*/
retrieve: async (id : string, user : LogOnSuccess) : Promise<Success | string | undefined> =>
apiResult<Success>(await fetch(apiUrl(`success/${id}`), reqInit('GET', user)), `retrieving success story ${id}`)
}
}

View File

@ -86,3 +86,35 @@ export interface Count {
/** The count being returned */
count : number
}
/** An entry in the list of success stories */
export interface StoryEntry {
/** The ID of this success story */
id : string
/** The ID of the citizen who recorded this story */
citizenId : string
/** The name of the citizen who recorded this story */
citizenName : string
/** When this story was recorded (date) */
recordedOn : string
/** Whether this story involves an opportunity that arose due to Jobs, Jobs, Jobs */
fromHere : boolean
/** Whether this report has a further story, or if it is simply a "found work" entry */
hasStory : boolean
}
/** A record of success finding employment */
export interface Success {
/** The ID of the success report */
id : string
/** The ID of the citizen who wrote this success report */
citizenId : string
/** When this success report was recorded (date) */
recordedOn : string
/** Whether the success was due, at least in part, to Jobs, Jobs, Jobs */
fromHere : boolean
/** The source of this success (listing or profile) */
source : string
/** The success story */
story : string | undefined
}

View File

@ -0,0 +1,34 @@
<template>
<template v-if="true">{{formatted}}</template>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { format, parseJSON } from 'date-fns'
import { utcToZonedTime } from 'date-fns-tz'
/**
* Parse a date from its JSON representation to a UTC-aligned date
*
* @param date The date string in JSON from JSON
* @returns A UTC JavaScript date
*/
export function parseToUtc (date : string) : Date {
return utcToZonedTime(parseJSON(date), Intl.DateTimeFormat().resolvedOptions().timeZone)
}
export default defineComponent({
name: 'FullDate',
props: {
date: {
type: String,
required: true
}
},
setup (props) {
return {
formatted: format(parseToUtc(props.date), 'PPP')
}
}
})
</script>

View File

@ -4,8 +4,8 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { format, parseJSON } from 'date-fns'
import { utcToZonedTime } from 'date-fns-tz'
import { format } from 'date-fns'
import { parseToUtc } from './FullDate.vue'
export default defineComponent({
name: 'FullDateTime',
@ -17,10 +17,7 @@ export default defineComponent({
},
setup (props) {
return {
formatted: format(
utcToZonedTime(
parseJSON(props.date), Intl.DateTimeFormat().resolvedOptions().timeZone),
'PPPppp')
formatted: format(parseToUtc(props.date), 'PPPppp')
}
}
})

View File

@ -12,7 +12,7 @@
<script lang="ts">
import { computed, defineComponent, ref } from 'vue'
import marked from 'marked'
import { markedOptions } from '../api'
import { markedOptions } from '@/api'
export default defineComponent({
name: 'MarkdownEditor',

View File

@ -22,7 +22,7 @@
<script lang="ts">
import { computed, defineComponent } from 'vue'
import { useStore } from '../../store'
import { useStore } from '@/store'
export default defineComponent({
name: 'AppNav',

View File

@ -4,7 +4,7 @@
<script lang="ts">
import { defineComponent } from 'vue'
import AudioClip from '../AudioClip.vue'
import AudioClip from '@/components/AudioClip.vue'
export default defineComponent({
name: 'TitleBar',

View File

@ -9,11 +9,11 @@ const routes: Array<RouteRecordRaw> = [
// Citizen URLs
{
path: '/citizen/authorized',
component: () => import(/* webpackChunkName: "logon" */ '../views/citizen/Authorized.vue')
component: () => import(/* webpackChunkName: "dashboard" */ '../views/citizen/Authorized.vue')
},
{
path: '/citizen/dashboard',
component: () => import(/* webpackChunkName: "logon" */ '../views/citizen/Dashboard.vue')
component: () => import(/* webpackChunkName: "dashboard" */ '../views/citizen/Dashboard.vue')
},
{
path: '/citizen/profile',
@ -48,11 +48,15 @@ const routes: Array<RouteRecordRaw> = [
// Success Story URLs
{
path: '/success-story/list',
component: () => import(/* webpackChunkName: "succview" */ '../views/success-story/StoryList.vue')
component: () => import(/* webpackChunkName: "success" */ '../views/success-story/StoryList.vue')
},
{
path: '/success-story/add',
component: () => import(/* webpackChunkName: "succedit" */ '../views/success-story/StoryAdd.vue')
},
{
path: '/success-story/view/:id',
component: () => import(/* webpackChunkName: "success" */ '../views/success-story/StoryView.vue')
}
]

View File

@ -16,7 +16,7 @@
<script lang="ts">
import { defineComponent } from 'vue'
import AudioClip from '../components/AudioClip.vue'
import AudioClip from '@/components/AudioClip.vue'
export default defineComponent({
name: 'Home',

View File

@ -8,7 +8,7 @@
<script lang="ts">
import { computed, defineComponent, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from '../../store'
import { useStore } from '@/store'
export default defineComponent({
name: 'Authorized',

View File

@ -23,8 +23,8 @@
</span>
</div>
<div v-else>
You do not have an employment profile established; click below (or &ldquo;Edit Profile&rdquo; in the menu)
to get started!
You do not have an employment profile established; click below (or &ldquo;Edit Profile&rdquo; in the
menu) to get started!
</div>
</v-card-text>
<v-card-actions>
@ -74,11 +74,11 @@
<script lang="ts">
import { defineComponent, Ref, ref } from 'vue'
import api, { LogOnSuccess, Profile } from '../../api'
import { useStore } from '../../store'
import FullDateTime from '../../components/FullDateTime.vue'
import LoadData from '../../components/LoadData.vue'
import { useRouter } from 'vue-router'
import api, { LogOnSuccess, Profile } from '@/api'
import { useStore } from '@/store'
import FullDateTime from '@/components/FullDateTime.vue'
import LoadData from '@/components/LoadData.vue'
export default defineComponent({
name: 'Dashboard',

View File

@ -9,7 +9,7 @@
<v-col cols="12" sm="10" md="8" lg="6">
<label for="realName">Real Name</label>
<input type="text" id="realName" v-model="realName" maxlength="255"
placeholder="Leave blank to use your NAS display name">
placeholder="Leave blank to use your NAS display name">
</v-col>
</v-row>
<v-row>
@ -34,7 +34,7 @@
<v-col cols="12" sm="6" md="8">
<label for="region" class="jjj-required">Region</label>
<input type="text" id="region" v-model="profile.region" maxlength="255"
placeholder="Country, state, geographic area, etc.">
placeholder="Country, state, geographic area, etc.">
</v-col>
</v-row>
<v-row>
@ -111,10 +111,10 @@
<script lang="ts">
import { computed, defineComponent, Ref, ref } from 'vue'
import { useRouter } from 'vue-router'
import api, { LogOnSuccess, Profile } from '../../api'
import MarkdownEditor from '../../components/MarkdownEditor.vue'
import LoadData from '../../components/LoadData.vue'
import { useStore } from '../../store'
import api, { LogOnSuccess, Profile } from '@/api'
import MarkdownEditor from '@/components/MarkdownEditor.vue'
import LoadData from '@/components/LoadData.vue'
import { useStore } from '@/store'
export default defineComponent({
name: 'EditProfile',

View File

@ -8,7 +8,7 @@
<script lang="ts">
import { defineComponent, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from '../../store'
import { useStore } from '@/store'
export default defineComponent({
name: 'LogOff',

View File

@ -36,9 +36,9 @@
import { computed, defineComponent, ref, Ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import marked from 'marked'
import LoadData from '../../components/LoadData.vue'
import { useStore } from '../../store'
import api, { LogOnSuccess, markedOptions, ProfileForView } from '../../api'
import api, { LogOnSuccess, markedOptions, ProfileForView } from '@/api'
import { useStore } from '@/store'
import LoadData from '@/components/LoadData.vue'
export default defineComponent({
name: 'ProfileEdit',

View File

@ -40,8 +40,8 @@
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { useRouter } from 'vue-router'
import api, { LogOnSuccess } from '../../api'
import { useStore } from '../../store'
import api, { LogOnSuccess } from '@/api'
import { useStore } from '@/store'
export default defineComponent({
name: 'DeletionOptions',

View File

@ -1,3 +1,79 @@
<template>
<p>TODO: convert this view</p>
<article>
<page-title title="Success Stories" />
<h3>Success Stories</h3>
<load-data :load="retrieveStories">
<table v-if="stories?.length > 0" class="table table-sm table-hover">
<thead>
<tr>
<th scope="col">Story</th>
<th scope="col">From</th>
<th scope="col">Found Here?</th>
<th scope="col">Recorded On</th>
</tr>
</thead>
<tbody>
<tr v-for="story in stories" :key="story.id">
<td>
<router-link v-if="story.hasStory" :to="`/success-story/view/${story.id}`">View</router-link>
<em v-else>None</em>
<template v-if="story.citizenId === user.citizenId">
~ <router-link :to="`/success-story/edit/${story.id}`">Edit</router-link>
</template>
</td>
<td>{{story.citizenName}}</td>
<td>
<strong v-if="story.fromHere">Yes</strong>
<template v-else>No</template>
</td>
<td><full-date :date="story.recordedOn" /></td>
</tr>
</tbody>
</table>
<p v-else>There are no success stories recorded <em>(yet)</em></p>
</load-data>
</article>
</template>
<script lang="ts">
import { defineComponent, ref, Ref } from 'vue'
import api, { LogOnSuccess, StoryEntry } from '@/api'
import { useStore } from '@/store'
import FullDate from '@/components/FullDate.vue'
import LoadData from '@/components/LoadData.vue'
export default defineComponent({
name: 'StoryList',
components: {
LoadData,
FullDate
},
setup () {
const store = useStore()
/** The currently logged-on user */
const user = store.state.user as LogOnSuccess
/** The success stories to be displayed */
const stories : Ref<StoryEntry[] | undefined> = ref(undefined)
/** Get all currently recorded stories */
const retrieveStories = async (errors : string[]) => {
const listResult = await api.success.list(user)
if (typeof listResult === 'string') {
errors.push(listResult)
} else if (typeof listResult === 'undefined') {
stories.value = []
} else {
stories.value = listResult
}
}
return {
retrieveStories,
stories,
user
}
}
})
</script>

View File

@ -0,0 +1,72 @@
<template>
<article>
<page-title title="Success Story" />
<load-data :load="retrieveStory">
<h3>{{citizenName}}&rsquo;s Success Story</h3>
<h4><full-date-time :date="story.recordedOn" /></h4>
<p v-if="story.fromHere"><em><strong>Found via Jobs, Jobs, Jobs</strong></em></p>
<hr>
<div v-if="story.story" v-html="successStory"></div>
</load-data>
</article>
</template>
<script lang="ts">
import { computed, defineComponent, Ref, ref } from 'vue'
import { useRoute } from 'vue-router'
import marked from 'marked'
import api, { LogOnSuccess, markedOptions, Success } from '@/api'
import { useStore } from '@/store'
import FullDateTime from '@/components/FullDateTime.vue'
import LoadData from '@/components/LoadData.vue'
export default defineComponent({
name: 'StoryView',
components: {
FullDateTime,
LoadData
},
setup () {
const store = useStore()
const route = useRoute()
/** The currently logged-on user */
const user = store.state.user as LogOnSuccess
/** The story to be displayed */
const story : Ref<Success | undefined> = ref(undefined)
/** The citizen's name (real, display, or NAS, whichever is found first) */
const citizenName = ref('')
/** Retrieve the success story */
const retrieveStory = async (errors : string []) => {
const storyResponse = await api.success.retrieve(route.params.id as string, user)
if (typeof storyResponse === 'string') {
errors.push(storyResponse)
return
}
if (typeof storyResponse === 'undefined') {
errors.push('Success story not found')
return
}
story.value = storyResponse
const citResponse = await api.citizen.retrieve(story.value.citizenId, user)
if (typeof citResponse === 'string') {
errors.push(citResponse)
} else if (typeof citResponse === 'undefined') {
errors.push('Citizen not found')
} else {
citizenName.value = citResponse.realName || citResponse.displayName || citResponse.naUser
}
}
return {
story,
retrieveStory,
citizenName,
successStory: computed(() => marked(story.value?.story || '', markedOptions))
}
}
})
</script>

View File

@ -189,7 +189,7 @@ type StoryEntry = {
/// The name of the citizen who recorded this story
citizenName : string
/// When this story was recorded
RecordedOn : Instant
recordedOn : Instant
/// Whether this story involves an opportunity that arose due to Jobs, Jobs, Jobs
fromHere : bool
/// Whether this report has a further story, or if it is simply a "found work" entry

13
vetur.config.js Normal file
View File

@ -0,0 +1,13 @@
// vetur.config.js
/** @type {import('vls').VeturConfig} */
module.exports = {
// override vscode settings
// Notice: It only affects the settings used by Vetur.
settings: {
// "vetur.useWorkspaceDependencies": true,
// "vetur.experimental.templateInterpolationService": true
},
projects: [
'./src/JobsJobsJobs/App'
]
}