diff --git a/.adonisrc.json b/.adonisrc.json index 00a85f5..5db2c02 100644 --- a/.adonisrc.json +++ b/.adonisrc.json @@ -22,6 +22,12 @@ "environment": [ "web" ] + }, + { + "file": "./start/validator", + "environment": [ + "web" + ] } ], "providers": [ diff --git a/app/Controllers/Http/Api/AuthorsController.ts b/app/Controllers/Http/Api/AuthorsController.ts index 4a30746..cd4e389 100644 --- a/app/Controllers/Http/Api/AuthorsController.ts +++ b/app/Controllers/Http/Api/AuthorsController.ts @@ -26,9 +26,9 @@ export default class AuthorsController { // users = users.whereRaw('name like %?%', [request.input('search')]) const searchTerm = request.input('filter'); authors - .where('first_name', 'ilike', `%${searchTerm}%`) - .orWhere('last_name', 'like', `%${searchTerm}%`) - .orWhere('email', 'like', `%${searchTerm}%`); + .whereILike('first_name', `%${searchTerm}%`) + .orWhereILike('last_name', `%${searchTerm}%`); + // .orWhere('email', 'like', `%${searchTerm}%`); } let persons = await authors; diff --git a/app/Controllers/Http/Submitter/DatasetController.ts b/app/Controllers/Http/Submitter/DatasetController.ts index d582db0..9e43469 100644 --- a/app/Controllers/Http/Submitter/DatasetController.ts +++ b/app/Controllers/Http/Submitter/DatasetController.ts @@ -3,11 +3,13 @@ import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; // import Role from 'App/Models/Role'; // import Database from '@ioc:Adonis/Lucid/Database'; import License from 'App/Models/License'; +import Project from 'App/Models/Project'; // import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm'; // import CreateUserValidator from 'App/Validators/CreateUserValidator'; // import UpdateUserValidator from 'App/Validators/UpdateUserValidator'; // import { RenderResponse } from '@ioc:EidelLev/Inertia'; import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator'; +import dayjs from 'dayjs'; enum TitleTypes { Main = 'Main', @@ -30,6 +32,8 @@ export default class DatasetController { public async create({ inertia }: HttpContextContract) { const licenses = await License.query().select('id', 'name_long').pluck('name_long', 'id'); + const projects = await Project.query().pluck('label', 'id'); + const doctypes = { analysisdata: { label: 'Analysis', value: 'analysisdata' }, measurementdata: { label: 'Measurements', value: 'measurementdata' }, @@ -47,13 +51,20 @@ export default class DatasetController { // Other: 'Other', // }; + // let test = Object.entries(TitleTypes).map(([key, value]) => ({id: key, value: value})) + // const languages = await Database.from('languages').select('*').where('active', true); return inertia.render('Submitter/Dataset/Create', { licenses: licenses, doctypes: doctypes, - titletypes: Object.values(TitleTypes).filter((x) => x.valueOf() !== 'Main'), - descriptiontypes: Object.values(DescriptionTypes).filter((x) => x.valueOf() !== 'Abstract'), + titletypes: Object.entries(TitleTypes) + .filter(([value]) => value !== 'Main') + .map(([key, value]) => ({ value: key, label: value })), + descriptiontypes: Object.entries(DescriptionTypes) + .filter(([value]) => value !== 'Abstract') + .map(([key, value]) => ({ value: key, label: value })), // descriptiontypes: DescriptionTypes + projects: projects, }); } @@ -68,7 +79,7 @@ export default class DatasetController { try { // Step 2 - Validate request body against the schema - + await request.validate({ schema: newDatasetSchema, messages: this.messages }); // console.log({ payload }); } catch (error) { @@ -81,10 +92,63 @@ export default class DatasetController { public async secondStep({ request, response }: HttpContextContract) { const newDatasetSchema = schema.create({ + // first step language: schema.string({ trim: true }, [ rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores ]), licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset + rights: schema.string([rules.equalTo('true')]), + // second step + type: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), + creating_corporation: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), + titles: schema.array([rules.minLength(1)]).members( + schema.object().members({ + value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), + type: schema.enum(Object.values(TitleTypes)), + language: schema.string({ trim: true }, [ + rules.minLength(2), + rules.maxLength(255), + rules.translatedLanguage('/language', 'type') + ]), + }), + ), + descriptions: schema.array([rules.minLength(1)]).members( + schema.object().members({ + value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), + type: schema.enum(Object.values(DescriptionTypes)), + language: schema.string({ trim: true }, [ + rules.minLength(2), + rules.maxLength(255), + rules.translatedLanguage('/language', 'type') + ]), + }), + ), + authors: schema.array([rules.minLength(1)]).members(schema.object().members({ email: schema.string({ trim: true }) })), + + // project_id: schema.number(), + }); + + try { + // Step 2 - Validate request body against the schema + await request.validate({ schema: newDatasetSchema, messages: this.messages }); + // console.log({ payload }); + } catch (error) { + // Step 3 - Handle errors + // return response.badRequest(error.messages); + throw error; + } + return response.redirect().back(); + } + + public async thirdStep({ request, response }: HttpContextContract) { + const newDatasetSchema = schema.create({ + // first step + language: schema.string({ trim: true }, [ + rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores + ]), + licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset + rights: schema.string([rules.equalTo('true')]), + // second step type: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), creating_corporation: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), titles: schema.array([rules.minLength(1)]).members( @@ -101,6 +165,10 @@ export default class DatasetController { language: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]), }), ), + authors: schema.array([rules.minLength(1)]).members(schema.object().members({ email: schema.string({ trim: true }) })), + // third step + project_id: schema.number.optional(), + embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]), }); try { @@ -114,7 +182,6 @@ export default class DatasetController { } return response.redirect().back(); } - // public async store({ request, response, session }: HttpContextContract) { // // node ace make:validator CreateUser // try { @@ -138,25 +205,31 @@ export default class DatasetController { // } public messages: CustomMessages = { - 'minLength': '{{ field }} must be at least {{ options.minLength }} characters long', - 'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long', - 'required': '{{ field }} is required', - 'unique': '{{ field }} must be unique, and this value is already taken', - // 'confirmed': '{{ field }} is not correct', - 'licences.minLength': 'at least {{ options.minLength }} permission must be defined', - 'licences.*.number': 'Define roles as valid numbers', + 'minLength': '{{ field }} must be at least {{ options.minLength }} characters long', + 'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long', + 'required': '{{ field }} is required', + 'unique': '{{ field }} must be unique, and this value is already taken', + // 'confirmed': '{{ field }} is not correct', + 'licences.minLength': 'at least {{ options.minLength }} permission must be defined', + 'licences.*.number': 'Define roles as valid numbers', 'rights.equalTo': 'you must agree to continue', - + 'titles.0.value.minLength': 'Main Title must be at least {{ options.minLength }} characters long', 'titles.0.value.required': 'Main Title is required', 'titles.*.value.required': 'Additional title is required, if defined', 'titles.*.type.required': 'Additional title type is required', 'titles.*.language.required': 'Additional title language is required', - + 'titles.*.language.translatedLanguage': 'The language of the translated title must be different from the language of the dataset', + 'descriptions.0.value.minLength': 'Main Abstract must be at least {{ options.minLength }} characters long', 'descriptions.0.value.required': 'Main Abstract is required', 'descriptions.*.value.required': 'Additional description is required, if defined', 'descriptions.*.type.required': 'Additional description type is required', 'descriptions.*.language.required': 'Additional description language is required', - }; + 'descriptions.*.language.translatedLanguage': 'The language of the translated description must be different from the language of the dataset', + + 'authors.minLength': 'at least {{ options.minLength }} author must be defined', + + 'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`, + }; } diff --git a/app/Models/Dataset.ts b/app/Models/Dataset.ts index 48c8f9e..54322d7 100644 --- a/app/Models/Dataset.ts +++ b/app/Models/Dataset.ts @@ -26,7 +26,7 @@ export default class Dataset extends BaseModel { public publisherName: string; @column.dateTime({ columnName: 'embargo_date' }) - public EmbargoDate: DateTime; + public embargoDate: DateTime; @column({}) public type: string; diff --git a/app/Models/Person.ts b/app/Models/Person.ts index e8735be..e101cd8 100644 --- a/app/Models/Person.ts +++ b/app/Models/Person.ts @@ -48,24 +48,24 @@ export default class Person extends BaseModel { }, autoCreate: true, }) - public registeredAt: DateTime; + public createdAt: DateTime; @computed({ serializeAs: 'name' }) public get fullName() { - return `${this.firstName} ${this.lastName} (${this.email})`; + return `${this.firstName} ${this.lastName}`; } - @computed() - public get progress(): number { - return 50; - } + // @computed() + // public get progress(): number { + // return 50; + // } - @computed() - public get created_at() { - return '2023-02-17 08:45:56'; - } + // @computed() + // public get created_at() { + // return '2023-03-21 08:45:00'; + // } @computed() public get datasetCount() { diff --git a/app/Models/Project.ts b/app/Models/Project.ts new file mode 100644 index 0000000..8c5080c --- /dev/null +++ b/app/Models/Project.ts @@ -0,0 +1,37 @@ +import { column, BaseModel, SnakeCaseNamingStrategy } from '@ioc:Adonis/Lucid/Orm'; +import { DateTime } from 'luxon'; + +export default class Project extends BaseModel { + public static namingStrategy = new SnakeCaseNamingStrategy(); + public static primaryKey = 'id'; + public static table = 'projects'; + public static selfAssignPrimaryKey = false; + + @column({ + isPrimary: true, + }) + public id: number; + + + @column({}) + public label: string; + + @column({}) + public name: string; + + @column({}) + public description: string; + + @column.dateTime({ + autoCreate: true, + }) + public created_at: DateTime; + + @column.dateTime({ + autoCreate: true, + autoUpdate: true + }) + public updated_at: DateTime; + + +} diff --git a/app/Models/utils.ts b/app/Models/utils.ts index 110591b..a706dc5 100644 --- a/app/Models/utils.ts +++ b/app/Models/utils.ts @@ -2,21 +2,18 @@ import Database, { // DatabaseQueryBuilderContract, QueryClientContract, TransactionClientContract, - } from "@ioc:Adonis/Lucid/Database"; - import Config from "@ioc:Adonis/Core/Config"; +} from '@ioc:Adonis/Lucid/Database'; +import Config from '@ioc:Adonis/Core/Config'; - export function getUserRoles( - userId: number, - trx?: TransactionClientContract - ): Promise> { - const { userRole } = Config.get("acl.joinTables"); +export function getUserRoles(userId: number, trx?: TransactionClientContract): Promise> { + const { userRole } = Config.get('acl.joinTables'); return ((trx || Database) as QueryClientContract | TransactionClientContract) - .query() - .from("roles") - .distinct("roles.slug") - .leftJoin(userRole, `${userRole}.role_id`, "roles.id") - .where(`${userRole}.user_id`, userId) - .then((res) => { - return res.map((r) => r.slug); - }); - } \ No newline at end of file + .query() + .from('roles') + .distinct('roles.slug') + .leftJoin(userRole, `${userRole}.role_id`, 'roles.id') + .where(`${userRole}.user_id`, userId) + .then((res) => { + return res.map((r) => r.slug); + }); +} diff --git a/contracts/validator.ts b/contracts/validator.ts new file mode 100644 index 0000000..91ca1ca --- /dev/null +++ b/contracts/validator.ts @@ -0,0 +1,9 @@ +declare module '@ioc:Adonis/Core/Validator' { + interface Rules { + translatedLanguage( + mainLanguageField: string, + typeField: string + ): Rule + } + } + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 53f076e..fc620bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "crypto": "^1.0.1", "dayjs": "^1.11.7", "luxon": "^3.2.1", + "notiwind": "^2.0.0", "pg": "^8.9.0", "proxy-addr": "^2.0.7", "reflect-metadata": "^0.1.13", @@ -65,7 +66,7 @@ "prettier": "^2.8.3", "tailwindcss": "^3.2.4", "ts-loader": "^9.4.2", - "typescript": "^4.9.4", + "typescript": "^4.9.5", "vue": "^3.2.47", "vue-loader": "^17.0.1", "youch": "^3.2.0", @@ -2522,9 +2523,9 @@ } }, "node_modules/@eidellev/inertia-adonisjs": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@eidellev/inertia-adonisjs/-/inertia-adonisjs-8.0.0.tgz", - "integrity": "sha512-nnlgS4P6OAjdV8cGcA1HdgdGoo5g6bdifTfSwjSSi3ijl+0aQgGK+R1cnLp+HCdEgQwnbxacAptVNd2r67u0bA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@eidellev/inertia-adonisjs/-/inertia-adonisjs-8.0.1.tgz", + "integrity": "sha512-TV4bDh2PEyOgf7lWRFPgaugxUXIHjXSV8I+qpRNQIWH+snWp7jYjoc8d/q1KsNewYRLCh2HpbMGQtbqgYEOMxw==", "dependencies": { "@types/md5": "^2.3.2", "html-entities": "^2.3.3", @@ -2556,9 +2557,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", - "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.1.tgz", + "integrity": "sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2848,9 +2849,9 @@ } }, "node_modules/@japa/run-failed-tests/node_modules/fs-extra": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", - "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -3084,9 +3085,9 @@ "dev": true }, "node_modules/@mdi/js": { - "version": "7.1.96", - "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.1.96.tgz", - "integrity": "sha512-wlrJs6Ryhaa5CqhK3FjTfMRnb/s7HeLkKMFqwQySkK86cdN1TGdzpSM3O4tsmzCA1dYBeTbXvOwSE/Y42cUrvA==", + "version": "7.2.96", + "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.2.96.tgz", + "integrity": "sha512-paR9M9ZT7rKbh2boksNUynuSZMHhqRYnEZOm/KrZTjQ4/FzyhjLHuvw/8XYzP+E7fS4+/Ms/82EN1pl/OFsiIA==", "dev": true }, "node_modules/@mrmlnc/readdir-enhanced": { @@ -3646,9 +3647,9 @@ "dev": true }, "node_modules/@types/eslint": { - "version": "8.21.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.2.tgz", - "integrity": "sha512-EMpxUyystd3uZVByZap1DACsMXvb82ypQnGn89e1Y0a+LYu3JJscUd/gqhRsVFDkaD2MIiWo0MT8EfXr3DGRKw==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.3.tgz", + "integrity": "sha512-fa7GkppZVEByMWGbTtE5MbmXWJTVbrjjaS8K6uQj+XtuuUv1fsuPAxhygfqLmsb/Ufb3CV8deFCpiMfAgi00Sw==", "dev": true, "peer": true, "dependencies": { @@ -3804,9 +3805,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.15.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", - "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==" + "version": "18.15.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.6.tgz", + "integrity": "sha512-YErOafCZpK4g+Rp3Q/PBgZNAsWKGunQTm9FA3/Pbcm0VCriTEzcrutQ/SxSc0rytAp0NoFWue669jmKhEtd0sA==" }, "node_modules/@types/pino": { "version": "6.3.12", @@ -3947,15 +3948,15 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz", - "integrity": "sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz", + "integrity": "sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/type-utils": "5.55.0", - "@typescript-eslint/utils": "5.55.0", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/type-utils": "5.56.0", + "@typescript-eslint/utils": "5.56.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -3981,14 +3982,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.55.0.tgz", - "integrity": "sha512-ppvmeF7hvdhUUZWSd2EEWfzcFkjJzgNQzVST22nzg958CR+sphy8A6K7LXQZd6V75m1VKjp+J4g/PCEfSCmzhw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.56.0.tgz", + "integrity": "sha512-sn1OZmBxUsgxMmR8a8U5QM/Wl+tyqlH//jTqCg8daTAmhAk26L2PFhcqPLlYBhYUJMZJK276qLXlHN3a83o2cg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/typescript-estree": "5.56.0", "debug": "^4.3.4" }, "engines": { @@ -4008,13 +4009,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.55.0.tgz", - "integrity": "sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", + "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0" + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4025,13 +4026,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.55.0.tgz", - "integrity": "sha512-ObqxBgHIXj8rBNm0yh8oORFrICcJuZPZTqtAFh0oZQyr5DnAHZWfyw54RwpEEH+fD8suZaI0YxvWu5tYE/WswA==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", + "integrity": "sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.55.0", - "@typescript-eslint/utils": "5.55.0", + "@typescript-eslint/typescript-estree": "5.56.0", + "@typescript-eslint/utils": "5.56.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -4052,9 +4053,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.55.0.tgz", - "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", + "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4065,13 +4066,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz", - "integrity": "sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", + "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4142,17 +4143,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.55.0.tgz", - "integrity": "sha512-FkW+i2pQKcpDC3AY6DU54yl8Lfl14FVGYDgBTyGKB75cCwV3KpkpTMFi9d9j2WAJ4271LR2HeC5SEWF/CZmmfw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", + "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/typescript-estree": "5.56.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -4190,12 +4191,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", - "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", + "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.56.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -5588,9 +5589,9 @@ } }, "node_modules/bonjour-service": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.0.tgz", - "integrity": "sha512-LVRinRB3k1/K0XzZ2p58COnWvkQknIY6sf0zF2rpErvcJXpMBttEPQSxK+HEXSS9VmpZlDoDnQWv8ftJT20B0Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", "dev": true, "dependencies": { "array-flatten": "^2.1.2", @@ -5826,9 +5827,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001467", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001467.tgz", - "integrity": "sha512-cEdN/5e+RPikvl9AHm4uuLXxeCNq8rFsQ+lPHTfe/OtypP3WwnVVbjn+6uBV7PaFL6xUFzTh+sSCOz1rKhcO+Q==", + "version": "1.0.30001469", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz", + "integrity": "sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g==", "dev": true, "funding": [ { @@ -6484,9 +6485,9 @@ "dev": true }, "node_modules/cosmiconfig": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.0.tgz", - "integrity": "sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", + "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", "dev": true, "dependencies": { "import-fresh": "^3.2.1", @@ -6626,9 +6627,9 @@ } }, "node_modules/css-declaration-sorter": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", - "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz", + "integrity": "sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew==", "dev": true, "engines": { "node": "^10 || ^12 || >=14" @@ -7413,25 +7414,35 @@ "integrity": "sha512-ORKH7rn/LEfgo0qrgoMMwFPfestNFvTO+dRLDmS+zgulhd9BVXfv/p+V6K3OyEeu2L0ysYp/71uHB31GIDe68A==" }, "node_modules/edge-lexer": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/edge-lexer/-/edge-lexer-4.0.10.tgz", - "integrity": "sha512-6hvRE0TkYyqogEGTU6GpNW3vf6PDA0U+zLEXIV4ZhsCRy2YX+FuprzitYpe69ZHC+BZdiFpdCn2Zqf1HOebqEg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/edge-lexer/-/edge-lexer-5.0.2.tgz", + "integrity": "sha512-MSpv6JRPD96eZl0uPo8gIvE9cPAXb1eNRfZKlQpFYJ2O8cNdZDpN8RvT2zpu4XqC63HWs/c938qc3dWN+GJhFw==", "dependencies": { - "edge-error": "^2.0.8" + "edge-error": "^3.0.0" } }, + "node_modules/edge-lexer/node_modules/edge-error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/edge-error/-/edge-error-3.0.0.tgz", + "integrity": "sha512-TJE2A6KFqBMIe3EPyl/5V8veGYcMfpEtSNKfZkrSkQAyz3kh27etQ2erU6o0lkAFqDjjUl11YVzBIO2ij5/Zhw==" + }, "node_modules/edge-parser": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/edge-parser/-/edge-parser-8.2.1.tgz", - "integrity": "sha512-6x2BdSqwU4Oo4IF+ppu3wchpzhZ8Oq05hBPGLYnTOSoSVrQThSV9PK9t+Oz47XQOQSb5V9fugUOYLuUy8iXZGg==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/edge-parser/-/edge-parser-8.2.2.tgz", + "integrity": "sha512-48iOLaOD4PAxIU0jnLRNXUL8G6ktO79bA4adOGXzNHg2M+mHfudFI7QPdF6IO8gW0NC1N0TPvwLWK3FVkE9bLQ==", "dependencies": { - "acorn": "^8.8.0", - "astring": "^1.8.3", - "edge-error": "^2.0.8", - "edge-lexer": "^4.0.10", + "acorn": "^8.8.2", + "astring": "^1.8.4", + "edge-error": "^3.0.0", + "edge-lexer": "^5.0.2", "js-stringify": "^1.0.2" } }, + "node_modules/edge-parser/node_modules/edge-error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/edge-error/-/edge-error-3.0.0.tgz", + "integrity": "sha512-TJE2A6KFqBMIe3EPyl/5V8veGYcMfpEtSNKfZkrSkQAyz3kh27etQ2erU6o0lkAFqDjjUl11YVzBIO2ij5/Zhw==" + }, "node_modules/edge-supercharged": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/edge-supercharged/-/edge-supercharged-3.1.1.tgz", @@ -7466,20 +7477,25 @@ } }, "node_modules/edge.js": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/edge.js/-/edge.js-5.5.0.tgz", - "integrity": "sha512-jH6g5wDMJB2xVt+hwSTTlx++hVVItZIPJgt8Q8of/zIt2391eD90IpvHZQM8cG8v2j8H6YdCJ3Rp998ze1BvJA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/edge.js/-/edge.js-5.5.1.tgz", + "integrity": "sha512-lAhEdj1tW2VOJsP5X38wSHjjaXXwoteZ+8PDAJ50rAKzBxpw4QsdXK2jVzMFgRoTMUf7C+x+Tp51R9yxGTLO0w==", "dependencies": { "@poppinss/inspect": "^1.0.1", "@poppinss/utils": "^5.0.0", - "edge-error": "^2.0.8", - "edge-lexer": "^4.0.10", + "edge-error": "^3.0.0", + "edge-lexer": "^5.0.2", "edge-parser": "^8.2.1", "js-stringify": "^1.0.2", "macroable": "^7.0.1", "stringify-attributes": "^2.0.0" } }, + "node_modules/edge.js/node_modules/edge-error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/edge-error/-/edge-error-3.0.0.tgz", + "integrity": "sha512-TJE2A6KFqBMIe3EPyl/5V8veGYcMfpEtSNKfZkrSkQAyz3kh27etQ2erU6o0lkAFqDjjUl11YVzBIO2ij5/Zhw==" + }, "node_modules/editorconfig": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", @@ -7532,9 +7548,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.332", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.332.tgz", - "integrity": "sha512-c1Vbv5tuUlBFp0mb3mCIjw+REEsgthRgNE8BlbEDKmvzb8rxjcVki6OkQP83vLN34s0XCxpSkq7AZNep1a6xhw==", + "version": "1.4.337", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.337.tgz", + "integrity": "sha512-W8gdzXG86mVPoc56eM8YA+QiLxaAxJ8cmDjxZgfhLLWVvZQxyA918w5tX2JEWApZta45T1/sYcmFHTsTOUE3nw==", "dev": true }, "node_modules/emittery": { @@ -7739,9 +7755,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz", - "integrity": "sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -10293,9 +10309,9 @@ } }, "node_modules/jest-util/node_modules/@types/yargs": { - "version": "17.0.22", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz", - "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "version": "17.0.23", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.23.tgz", + "integrity": "sha512-yuogunc04OnzGQCrfHx+Kk883Q4X0aSwmYZhKjI21m+SVYzjIbrWl8dOOwSv5hf2Um2pdCOXWo9isteZTNXUZQ==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -10415,9 +10431,9 @@ } }, "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", "dev": true, "funding": { "type": "opencollective", @@ -10909,9 +10925,9 @@ } }, "node_modules/marked": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", - "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, "bin": { "marked": "bin/marked.js" @@ -11224,6 +11240,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==" + }, "node_modules/mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -11574,6 +11595,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/notiwind": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/notiwind/-/notiwind-2.0.0.tgz", + "integrity": "sha512-TRl3vTVnBEqvNQZM15oFtS63S6hGobkRIWTyvcGyNcOzsTPb8WybE6vuXTelUgUbMqQooSae1L25cqOyFMjTMg==", + "dependencies": { + "mitt": "^3.0.0" + }, + "peerDependencies": { + "vue": "^3.2.40" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -12424,9 +12456,9 @@ } }, "node_modules/pino-pretty/node_modules/sonic-boom": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.1.tgz", - "integrity": "sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz", + "integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==", "dependencies": { "atomic-sleep": "^1.0.0" } @@ -13270,9 +13302,9 @@ } }, "node_modules/prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.6.tgz", + "integrity": "sha512-mtuzdiBbHwPEgl7NxWlqOkithPyp4VN93V7VeHVWBF+ad3I5avc0RVDT4oImXQy9H/AqxA2NSQH8pSxHW6FYbQ==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -14192,9 +14224,9 @@ } }, "node_modules/set-cookie-parser": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz", - "integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", "dev": true }, "node_modules/set-value": { @@ -16135,9 +16167,9 @@ } }, "node_modules/webpack": { - "version": "5.76.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", - "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", + "version": "5.76.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.3.tgz", + "integrity": "sha512-18Qv7uGPU8b2vqGeEEObnfICyw2g39CHlDEK4I7NK13LOur1d0HGmGNKGT58Eluwddpn3oEejwvBPoP4M7/KSA==", "dev": true, "peer": true, "dependencies": { @@ -16329,9 +16361,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.12.0.tgz", - "integrity": "sha512-XRN9YRnvOj3TQQ5w/0pR1y1xDcVnbWtNkTri46kuEbaWUPTHsWUvOyAAI7PZHLY+hsFki2kRltJjKMw7e+IiqA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.1.tgz", + "integrity": "sha512-5tWg00bnWbYgkN+pd5yISQKDejRBYGEw15RaEEslH+zdbNDxxaZvEAO2WulaSaFKb5n3YG8JXsGaDsut1D0xdA==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -16379,6 +16411,9 @@ "webpack": "^4.37.0 || ^5.0.0" }, "peerDependenciesMeta": { + "webpack": { + "optional": true + }, "webpack-cli": { "optional": true } diff --git a/package.json b/package.json index 4a9925c..11c531e 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "prettier": "^2.8.3", "tailwindcss": "^3.2.4", "ts-loader": "^9.4.2", - "typescript": "^4.9.4", + "typescript": "^4.9.5", "vue": "^3.2.47", "vue-loader": "^17.0.1", "youch": "^3.2.0", @@ -89,6 +89,7 @@ "crypto": "^1.0.1", "dayjs": "^1.11.7", "luxon": "^3.2.1", + "notiwind": "^2.0.0", "pg": "^8.9.0", "proxy-addr": "^2.0.7", "reflect-metadata": "^0.1.13", diff --git a/resources/js/Components/BaseLevel.vue b/resources/js/Components/BaseLevel.vue index 9461626..b25bb12 100644 --- a/resources/js/Components/BaseLevel.vue +++ b/resources/js/Components/BaseLevel.vue @@ -1,37 +1,34 @@ diff --git a/resources/js/Components/FormControl.vue b/resources/js/Components/FormControl.vue index 47dbd35..4dfd494 100644 --- a/resources/js/Components/FormControl.vue +++ b/resources/js/Components/FormControl.vue @@ -112,7 +112,7 @@ if (props.ctrlKFocus) {
diff --git a/resources/js/Components/NotificationToast.vue b/resources/js/Components/NotificationToast.vue new file mode 100644 index 0000000..a43f40c --- /dev/null +++ b/resources/js/Components/NotificationToast.vue @@ -0,0 +1,66 @@ + + + diff --git a/resources/js/Components/PillTag.vue b/resources/js/Components/PillTag.vue index 844025b..9359e3d 100644 --- a/resources/js/Components/PillTag.vue +++ b/resources/js/Components/PillTag.vue @@ -1,56 +1,42 @@ diff --git a/resources/js/Components/SearchAutocomplete.vue b/resources/js/Components/SearchAutocomplete.vue index 9aafdda..50578f0 100644 --- a/resources/js/Components/SearchAutocomplete.vue +++ b/resources/js/Components/SearchAutocomplete.vue @@ -17,68 +17,72 @@ --> -
- - - - -
- - -
    -
  • +
    + + + + +
    + + +
      +
    • - - - - + > + + + + - - - {{ item }} - -
    • -
    - + + {{ item }} + + - {{ result.email }} +
  • +
+
diff --git a/resources/js/Components/TablePersons.vue b/resources/js/Components/TablePersons.vue new file mode 100644 index 0000000..293ae6b --- /dev/null +++ b/resources/js/Components/TablePersons.vue @@ -0,0 +1,157 @@ + + + diff --git a/resources/js/Components/UserAvatarCurrentUser.vue b/resources/js/Components/UserAvatarCurrentUser.vue index 07cf8e2..3669138 100644 --- a/resources/js/Components/UserAvatarCurrentUser.vue +++ b/resources/js/Components/UserAvatarCurrentUser.vue @@ -1,15 +1,12 @@ \ No newline at end of file + + diff --git a/resources/js/Dataset.ts b/resources/js/Dataset.ts index 9ff7121..77494be 100644 --- a/resources/js/Dataset.ts +++ b/resources/js/Dataset.ts @@ -1,7 +1,7 @@ import { Ref } from 'vue'; export interface Dataset { - [key: string]: string | Ref| boolean | Array | (IErrorMessage | undefined); + [key: string]: string | Ref<string>| boolean | Array<Title> | Array<Description>| Array<Person> | number | (IErrorMessage | undefined); language: Ref<string>; // licenses: Array<number>; rights: boolean; @@ -9,6 +9,10 @@ export interface Dataset { creating_corporation: string; titles: Array<Title>; descriptions: Array<Description>; + authors: Array<Person>; + contributors: Array<Person>; + project_id?: number; + embargo_date?: string, errors?: IErrorMessage; // async (user): Promise<void>; } @@ -25,6 +29,16 @@ export interface Description { language: string | Ref<string>; } +export interface Person { + id: number; + name: string; + email: string; + name_type: string; + identifier_orcid: string; + datasetCount: string; + created_at: string; +} + interface IErrorMessage { [key: string]: Array<string>; } \ No newline at end of file diff --git a/resources/js/Layouts/LayoutAuthenticated.vue b/resources/js/Layouts/LayoutAuthenticated.vue index f5691c7..6d88f7e 100644 --- a/resources/js/Layouts/LayoutAuthenticated.vue +++ b/resources/js/Layouts/LayoutAuthenticated.vue @@ -4,6 +4,7 @@ import { StyleService } from '@/Stores/style'; import NavBar from '@/Components/NavBar.vue'; import AsideMenu from '@/Components/AsideMenu.vue'; import FooterBar from '@/Components/FooterBar.vue'; +import NotificationToast from '@/Components/NotificationToast.vue'; const styleService = StyleService(); @@ -34,4 +35,5 @@ const layoutService = LayoutService(); <FooterBar /> </div> </div> + <NotificationToast></NotificationToast> </template> diff --git a/resources/js/Pages/Submitter/Dataset/Create.vue b/resources/js/Pages/Submitter/Dataset/Create.vue index 36fd00f..f455520 100644 --- a/resources/js/Pages/Submitter/Dataset/Create.vue +++ b/resources/js/Pages/Submitter/Dataset/Create.vue @@ -1,8 +1,16 @@ <script setup lang="ts"> import { Head, useForm } from '@inertiajs/vue3'; import { ref } from 'vue'; -import { Dataset, Title } from '@/Dataset'; -import { mdiDatabasePlus, mdiMinusCircle, mdiPlusCircle, mdiFinance, mdiInformationOutline, mdiBookOpenPageVariant } from '@mdi/js'; +import { Dataset, Description, Title } from '@/Dataset'; +import { + mdiDatabasePlus, + mdiMinusCircle, + mdiPlusCircle, + mdiFinance, + mdiInformationOutline, + mdiBookOpenPageVariant, + mdiImageText, +} from '@mdi/js'; import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue'; import SectionMain from '@/Components/SectionMain.vue'; import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue'; @@ -24,6 +32,10 @@ import IconLanguage from '@/Components/Icons/Language.vue'; import IconRecommendet from '@/Components/Icons/Recommendet.vue'; import IconConfirm from '@/Components/Icons/Confirm.vue'; import SearchAutocomplete from '@/Components/SearchAutocomplete.vue'; +import TablePersons from '@/Components/TablePersons.vue'; +import { MainService } from '@/Stores/main'; +import { notify } from '@/notiwind'; +// import NotificationToast from '@/Components/NotificationToast.vue'; const props = defineProps({ licenses: { @@ -42,12 +54,18 @@ const props = defineProps({ type: Object, default: () => ({}), }, + projects: { + type: Object, + default: () => ({}), + }, errors: { type: Object, default: () => ({}), }, }); +const mainService = MainService(); + // const form = useForm({ // language: '', // licenses: [], @@ -56,17 +74,57 @@ const props = defineProps({ // }); // let language: (string | Ref<string>) = ref(''); let language = ref(''); -let dataset = { - language: language, - licenses: [], - rights: false, - type: '', - creating_corporation: 'Tethys RDR', - titles: [{ value: '', type: 'Main', language: language }], - descriptions: [{ value: '', type: 'Abstract', language: language }], - // errors: undefined, -}; -// const form = useForm({ +let dataset: Dataset; +if (Object.keys(mainService.dataset).length == 0) { + // language = ref(''); + dataset = { + language: language, + licenses: [], + rights: false, + type: '', + creating_corporation: 'Tethys RDR', + titles: [{ value: '', type: 'Main', language: language }], + descriptions: [{ value: '', type: 'Abstract', language: language }], + authors: [], + contributors: [], + project_id: undefined, + embargo_date: '', + // errors: undefined, + }; + // mainService.setDataset(dataset, language); +} else { + // console.log(mainService.dataset); + language = ref(mainService.dataset.language); + + // dataset = mainService.dataset; + dataset = { + language: language, + licenses: mainService.dataset.licenses, + rights: mainService.dataset.rights, + type: mainService.dataset.type, + creating_corporation: mainService.dataset.creating_corporation, + titles: mainService.dataset.titles, + descriptions: mainService.dataset.descriptions, + authors: mainService.dataset.authors, + contributors: mainService.dataset.contributors, + project_id: mainService.dataset.project_id, + embargo_date: mainService.dataset.embargo_date, + }; + for (let index in mainService.dataset.titles) { + let title: Title = mainService.dataset.titles[index]; + if (title.type == 'Main') { + title.language = language; + } + } + for (let index in mainService.dataset.descriptions) { + let description: Description = mainService.dataset.descriptions[index]; + if (description.type == 'Abstract') { + description.language = language; + } + } +} + +// const form = useForm<Dataset>({ // language: language, // licenses: [], // rights: false, @@ -98,9 +156,13 @@ const formStep = ref(1); // }; const nextStep = async () => { - let route = stardust.route('dataset.first.step'); - if (formStep.value == 2) { + let route; + if (formStep.value == 1) { + route = stardust.route('dataset.first.step'); + } else if (formStep.value == 2) { route = stardust.route('dataset.second.step'); + } else if (formStep.value == 3) { + route = stardust.route('dataset.third.step'); } // formStep.value++; await form @@ -111,6 +173,8 @@ const nextStep = async () => { .post(route, { form, onSuccess: () => { + // console.log(form.data()); + mainService.setDataset(form.data()); formStep.value++; }, }); @@ -137,6 +201,29 @@ const addDescription = () => { const removeDescription = (key) => { form.descriptions.splice(key, 1); }; + +const onAddAuthor = (person) => { + if (form.authors.filter((e) => e.id === person.id).length > 0) { + notify({ type: 'warning', title: 'Warning', text: 'person is already defined as author' }, 4000); + } else if (form.contributors.filter((e) => e.id === person.id).length > 0) { + notify({ type: 'warning', title: 'Warning', text: 'person is already defined as contributor' }); + } else { + form.authors.push(person); + notify({ type: 'info', text: 'person has been successfully added as author' }); + } +}; +const onAddContributor = (person) => { + if (form.contributors.filter((e) => e.id === person.id).length > 0) { + notify({ type: 'warning', title: 'Warning', text: 'person is already defined as contributor' }, 4000); + } else if (form.authors.filter((e) => e.id === person.id).length > 0) { + notify({ type: 'warning', title: 'Warning', text: 'person is already defined as author' }, 4000); + } else { + // person.pivot = { contributor_type: '' }; + // // person.pivot = { name_type: '', contributor_type: '' }; + form.contributors.push(person); + notify({ type: 'info', text: 'person has been successfully added as contributor' }, 4000); + } +}; </script> <template> @@ -161,6 +248,7 @@ const removeDescription = (key) => { <SectionTitleLineWithButton :icon="mdiDatabasePlus" title="Submit dataset" main> <!-- <BaseButton :route-name="stardust.route('user.index')" :icon="mdiArrowLeftBoldOutline" label="Back" color="white" rounded-full small /> --> + {{ formStep }} </SectionTitleLineWithButton> <CardBox> @@ -202,7 +290,7 @@ const removeDescription = (key) => { :type="'select'" placeholder="[Enter Language]" :errors="form.errors.language" - :options="['de', 'en']" + :options="{ de: 'de', en: 'en' }" > <div class="text-red-400 text-sm" v-if="form.errors.language"> {{ form.errors.language.join(', ') }} @@ -298,7 +386,7 @@ const removeDescription = (key) => { <!-- titles --> <CardBox - class="mb-6" + class="mb-6 shadow" :has-form-data="true" title="Titles" :icon="mdiFinance" @@ -394,7 +482,7 @@ const removeDescription = (key) => { required v-model="form.titles[index].language" type="select" - :options="['de', 'en']" + :options="{ de: 'de', en: 'en' }" placeholder="[select title language]" > <div class="text-red-400 text-sm" v-if="form.errors[`titles.${index}.language`]"> @@ -410,7 +498,8 @@ const removeDescription = (key) => { <!-- Descriptions --> <CardBox - class="mb-6" + :icon="mdiImageText" + class="mb-6 shadow" :has-form-data="true" title="Descriptions" :header-icon="mdiPlusCircle" @@ -523,7 +612,7 @@ const removeDescription = (key) => { required v-model="form.descriptions[index].language" type="select" - :options="['de', 'en']" + :options="{ de: 'de', en: 'en' }" placeholder="[select title language]" > <div @@ -539,31 +628,76 @@ const removeDescription = (key) => { </div> </CardBox> - <CardBox class="mb-6" :has-form-data="true" title="Authors" :icon="mdiBookOpenPageVariant" > + <!-- authors --> + <CardBox class="mb-6 shadow" has-table title="Authors" :icon="mdiBookOpenPageVariant"> <SearchAutocomplete source="/api/persons" :response-property="'first_name'" placeholder="search in person table...." + v-on:person="onAddAuthor" ></SearchAutocomplete> + + <TablePersons :persons="form.authors" v-if="form.authors.length > 0" /> + <div class="text-red-400 text-sm" v-if="errors.authors && Array.isArray(errors.authors)"> + {{ errors.authors.join(', ') }} + </div> + </CardBox> + + <!-- contributors --> + <CardBox class="mb-6 shadow" has-table title="Contributors" :icon="mdiBookOpenPageVariant"> + <SearchAutocomplete + source="/api/persons" + :response-property="'first_name'" + placeholder="search in person table...." + v-on:person="onAddContributor" + ></SearchAutocomplete> + + <TablePersons :persons="form.contributors" v-if="form.contributors.length > 0" /> </CardBox> - <!-- <SectionTitleLineWithButton :icon="mdiChartPie" title="Trends overview (to do publications per year)" class="flex flex-col md:flex-row" > - </SectionTitleLineWithButton> --> </div> + <!-- <label>To Do: Recommendet</label> --> <div v-if="formStep == 3"> - <label>To Do: Recommendet</label> - <!-- <div class="w-full mx-2 flex-1 svelte-1l8159u"> - <div class="font-bold h-6 mt-3 text-gray-600 text-xs leading-8 uppercase">Username</div> - <div class="bg-white my-2 p-1 flex border border-gray-200 rounded svelte-1l8159u"> - <input placeholder="Just a hint.." class="p-1 px-2 appearance-none outline-none w-full text-gray-800" /> - </div> - </div> - <div class="w-full mx-2 flex-1 svelte-1l8159u"> - <div class="font-bold h-6 mt-3 text-gray-600 text-xs leading-8 uppercase">Your Email</div> - <div class="bg-white my-2 p-1 flex border border-gray-200 rounded svelte-1l8159u"> - <input placeholder="jhon@doe.com" class="p-1 px-2 appearance-none outline-none w-full text-gray-800" /> - </div> - </div> --> + <div class="flex flex-col md:flex-row"> + <FormField + label="Project.." + help="project is optional" + :class="{ 'text-red-400': errors.project_id }" + class="w-full mx-2 flex-1" + > + <FormControl + required + v-model="form.project_id" + :type="'select'" + placeholder="[Select Project]" + :errors="form.errors.project_id" + :options="projects" + > + <div class="text-red-400 text-sm" v-if="form.errors.project_id"> + {{ form.errors.project_id.join(', ') }} + </div> + </FormControl> + </FormField> + + <FormField + label="Embargo Date.." + help="embargo date is optional" + :class="{ 'text-red-400': errors.embargo_date }" + class="w-full mx-2 flex-1" + > + <FormControl + required + v-model="form.embargo_date" + :type="'date'" + placeholder="date('y-m-d')" + :errors="form.errors.embargo_date" + > + <div class="text-red-400 text-sm" v-if="form.errors.embargo_date"> + {{ form.errors.embargo_date.join(', ') }} + </div> + </FormControl> + </FormField> + </div> </div> <div v-if="formStep == 4"> diff --git a/resources/js/Stores/main.ts b/resources/js/Stores/main.ts index 35b8651..95e5ba3 100644 --- a/resources/js/Stores/main.ts +++ b/resources/js/Stores/main.ts @@ -1,10 +1,13 @@ import { defineStore } from 'pinia'; import axios from 'axios'; +import { Dataset } from '@/Dataset'; -interface Person { +export interface Person { id: number; name: string; email: string; + name_type: string; + identifier_orcid: string; datasetCount: string; created_at: string; } @@ -12,7 +15,7 @@ interface Person { interface TransactionItem { amount: number; account: string; - name: string; + name: string; date: string; type: string; business: string; @@ -31,8 +34,13 @@ export const MainService = defineStore('main', { /* Sample data for starting dashboard(commonly used) */ clients: [], history: [] as Array<TransactionItem>, + + // api based data authors: [] as Array<Person>, + // persons: [] as Array<Person>, datasets: [], + + dataset: {} as Dataset, }), actions: { // payload = authenticated user @@ -48,6 +56,27 @@ export const MainService = defineStore('main', { } }, + setDataset(payload) { + this.dataset = payload; + + // this.dataset = { + // language: language, + // licenses: payload.licenses, + // rights: payload.rights, + // type: payload.type, + // creating_corporation: payload.creating_corporation, + // titles: payload.titles, + // descriptions: payload.descriptions, + // authors: payload.authors, + // project_id: payload.project_id, + // embargo_date: payload.embargo_date, + // } as Dataset; + // for (let index in payload.titles) { + // let title = payload.titles[index]; + // title.language = language; + // } + }, + fetch(sampleDataKey) { // sampleDataKey= clients or history axios diff --git a/resources/js/notiwind.ts b/resources/js/notiwind.ts new file mode 100644 index 0000000..5ee4f97 --- /dev/null +++ b/resources/js/notiwind.ts @@ -0,0 +1,16 @@ +// notiwind.ts +import { + createNotifier, + NotificationGroup, + defineNotificationComponent, + } from "notiwind"; + + export type NotificationSchema = { + type: string; + title?: string; + text: string; + }; + + export const notify = createNotifier<NotificationSchema>(); + export const Notification = defineNotificationComponent<NotificationSchema>(); + export { NotificationGroup }; \ No newline at end of file diff --git a/start/routes.ts b/start/routes.ts index f3346fb..4fe55a9 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -133,6 +133,9 @@ Route.group(() => { Route.post('/dataset/second/second-step', 'DatasetController.secondStep') .as('dataset.second.step') .middleware(['auth', 'can:dataset-submit']); + Route.post('/dataset/second/third-step', 'DatasetController.thirdStep') + .as('dataset.third.step') + .middleware(['auth', 'can:dataset-submit']); // Route.get('/user/:id', 'UsersController.show').as('user.show').where('id', Route.matchers.number()); // Route.get('/user/:id/edit', 'UsersController.edit').as('user.edit').where('id', Route.matchers.number()); // Route.put('/user/:id/update', 'UsersController.update').as('user.update').where('id', Route.matchers.number()); diff --git a/start/validator.ts b/start/validator.ts new file mode 100644 index 0000000..e846c66 --- /dev/null +++ b/start/validator.ts @@ -0,0 +1,46 @@ +/* +|-------------------------------------------------------------------------- +| Preloaded File +|-------------------------------------------------------------------------- +| +| Any code written inside this file will be executed during the application +| boot. +https://issuehunt.io/r/adonisjs/validator/issues/84 +| +*/ +// import { string } from '@ioc:Adonis/Core/Helpers'; +import { validator } from '@ioc:Adonis/Core/Validator'; + +validator.rule( + 'translatedLanguage', + (value, [mainLanguageField, typeField], { root, tip, pointer, arrayExpressionPointer, errorReporter }) => { + if (typeof value !== 'string') { + return; + } + // const fieldValue = validator. getValue(data, field) + // this should return the "category_id" value present in "root", but i got undefined + const mainLanguage = validator.helpers.getFieldValue(mainLanguageField, root, tip); + const type = validator.helpers.getFieldValue(typeField, root, tip); + + if (type && type == 'Translated') { + if (value == mainLanguage) { + errorReporter.report( + pointer, + 'translatedLanguage', // Keep an eye on this + 'translatedLanguage validation failed', + arrayExpressionPointer, + { mainLanguage }, + ); + } + } + + // if (value !== string.camelCase(value)) { + // options.errorReporter.report( + // options.pointer, + // 'camelCase', + // 'camelCase validation failed', + // options.arrayExpressionPointer + // ); + // } + }, +);