- second commit

This commit is contained in:
Kaimbacher 2023-03-17 16:13:37 +01:00
parent 4fc3bb0a01
commit 59a99ff3c8
61 changed files with 2625 additions and 1182 deletions

View File

@ -8,7 +8,7 @@ README.md
# Docker stuff
.dockerignore
Dockerfile*
docker-compose.yml

11
.prettierrc Normal file
View File

@ -0,0 +1,11 @@
{
"trailingComma": "all",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"useTabs": false,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 140
}

View File

@ -45,7 +45,7 @@ FROM base AS production
# Copy package.* to the working directory with active user
COPY --chown=node:node ./package*.json ./
# We run NPM CI to install the exact versions of dependencies
# We run NPM CI to install the exact versions of production dependencies
RUN npm ci --omit=dev
# Copy files to the working directory from the build folder the user
COPY --chown=node:node --from=build /home/node/app/build .

View File

@ -117,7 +117,7 @@ export default class UsersController {
const roles = await Role.query().pluck('name', 'id');
// const userHasRoles = user.roles;
const userHasRoles = await user.related('roles').query().orderBy('name').pluck('id');
// let test = Object.keys(userHasRoles).map((key) => userHasRoles[key]);
return inertia.render('Admin/User/Edit', {
roles: roles,
user: user,

View File

@ -4,20 +4,34 @@ import Person from 'App/Models/Person';
// node ace make:controller Author
export default class AuthorsController {
public async index({}: HttpContextContract) {
// select * from gba.persons
// where exists (select * from gba.documents inner join gba.link_documents_persons on "documents"."id" = "link_documents_persons"."document_id"
// where ("link_documents_persons"."role" = 'author') and ("persons"."id" = "link_documents_persons"."person_id"));
const authors = await Person.query()
.whereHas('datasets', (dQuery) => {
dQuery.wherePivot('role', 'author');
})
.withCount('datasets', (query) => {
query.as('datasets_count');
});
public async index({}: HttpContextContract) {
// select * from gba.persons
// where exists (select * from gba.documents inner join gba.link_documents_persons on "documents"."id" = "link_documents_persons"."document_id"
// where ("link_documents_persons"."role" = 'author') and ("persons"."id" = "link_documents_persons"."person_id"));
const authors = await Person.query()
.whereHas('datasets', (dQuery) => {
dQuery.wherePivot('role', 'author')
})
.withCount('datasets', (query) => {
query.as('datasets_count')
});
return authors;
}
return authors;
}
public async persons({ request }: HttpContextContract) {
const authors = Person.query().where('status', true);
if (request.input('filter')) {
// 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}%`);
}
let persons = await authors;
return persons;
}
}

View File

@ -0,0 +1,162 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
// import User from 'App/Models/User';
// import Role from 'App/Models/Role';
// import Database from '@ioc:Adonis/Lucid/Database';
import License from 'App/Models/License';
// 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';
enum TitleTypes {
Main = 'Main',
Sub = 'Sub',
Alternative = 'Alternative',
Translated = 'Translated',
Other = 'Other',
}
enum DescriptionTypes {
Abstract = 'Abstract',
Methods = 'Methods',
Series_information = 'Series_information',
Technical_info = 'Technical_info',
Translated = 'Translated',
Other = 'Other',
}
export default class DatasetController {
public async create({ inertia }: HttpContextContract) {
const licenses = await License.query().select('id', 'name_long').pluck('name_long', 'id');
const doctypes = {
analysisdata: { label: 'Analysis', value: 'analysisdata' },
measurementdata: { label: 'Measurements', value: 'measurementdata' },
monitoring: 'Monitoring',
remotesensing: 'Remote Sensing',
gis: 'GIS',
models: 'Models',
mixedtype: 'Mixed Type',
};
// const titletypes = {
// Sub: 'Sub',
// Alternative: 'Alternative',
// Translated: 'Translated',
// Other: 'Other',
// };
// 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'),
// descriptiontypes: DescriptionTypes
});
}
public async firstStep({ request, response }: HttpContextContract) {
const newDatasetSchema = schema.create({
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')]),
});
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 secondStep({ request, response }: HttpContextContract) {
const newDatasetSchema = schema.create({
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
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)]),
}),
),
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)]),
}),
),
});
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 store({ request, response, session }: HttpContextContract) {
// // node ace make:validator CreateUser
// try {
// // Step 2 - Validate request body against the schema
// await request.validate(CreateUserValidator);
// // console.log({ payload });
// } catch (error) {
// // Step 3 - Handle errors
// // return response.badRequest(error.messages);
// throw error;
// }
// const input = request.only(['login', 'email', 'password']);
// const user = await User.create(input);
// if (request.input('roles')) {
// const roles: Array<number> = request.input('roles');
// await user.related('roles').attach(roles);
// }
// session.flash('message', 'User has been created successfully');
// return response.redirect().toRoute('user.index');
// }
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',
'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',
'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',
};
}

37
app/Models/License.ts Normal file
View File

@ -0,0 +1,37 @@
import { column, BaseModel, SnakeCaseNamingStrategy } from '@ioc:Adonis/Lucid/Orm';
export default class License extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'id';
public static table = 'document_licences';
public static selfAssignPrimaryKey = false;
@column({
isPrimary: true,
})
public id: number;
@column({})
public active: boolean;
@column({})
public langauge: string;
@column({})
public link_licence: string;
@column({})
public link_logo: string;
@column({})
public display_name: string;
@column({})
public name_long: string;
@column({})
public name: string;
@column({})
public sortOrder: number;
}

View File

@ -54,7 +54,7 @@ export default class Person extends BaseModel {
serializeAs: 'name'
})
public get fullName() {
return this.firstName + ' ' + this.lastName;
return `${this.firstName} ${this.lastName} (${this.email})`;
}
@computed()

View File

@ -3,101 +3,101 @@ import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
// import { Request } from '@adonisjs/core/build/standalone';
export default class UpdateUserValidator {
protected ctx: HttpContextContract;
public schema;
protected ctx: HttpContextContract;
public schema;
constructor(ctx: HttpContextContract) {
this.ctx = ctx;
this.schema = this.createSchema();
}
constructor(ctx: HttpContextContract) {
this.ctx = ctx;
this.schema = this.createSchema();
}
// public get schema() {
// return this._schema;
// }
// public get schema() {
// return this._schema;
// }
private createSchema() {
return schema.create({
login: schema.string({ trim: true }, [
rules.minLength(3),
rules.maxLength(50),
rules.unique({
table: 'accounts',
column: 'login',
// whereNot: { id: this.refs.id }
whereNot: { id: this.ctx?.params.id },
}),
// rules.regex(/^[a-zA-Z0-9-_]+$/),
//Must be alphanumeric with hyphens or underscores
]),
email: schema.string({}, [
rules.email(),
rules.unique({ table: 'accounts', column: 'email', whereNot: { id: this.ctx?.params.id } }),
]),
password: schema.string.optional([rules.confirmed(), rules.minLength(6)]),
roles: schema.array.optional([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
});
}
private createSchema() {
return schema.create({
login: schema.string({ trim: true }, [
rules.minLength(3),
rules.maxLength(50),
rules.unique({
table: 'accounts',
column: 'login',
// whereNot: { id: this.refs.id }
whereNot: { id: this.ctx?.params.id },
}),
// rules.regex(/^[a-zA-Z0-9-_]+$/),
//Must be alphanumeric with hyphens or underscores
]),
email: schema.string({}, [
rules.email(),
rules.unique({ table: 'accounts', column: 'email', whereNot: { id: this.ctx?.params.id } }),
]),
password: schema.string.optional([rules.confirmed(), rules.minLength(6)]),
roles: schema.array.optional([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
});
}
/*
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
*
* For example:
* 1. The username must be of data type string. But then also, it should
* not contain special characters or numbers.
* ```
* schema.string({}, [ rules.alpha() ])
* ```
*
* 2. The email must be of data type string, formatted as a valid
* email. But also, not used by any other user.
* ```
* schema.string({}, [
* rules.email(),
* rules.unique({ table: 'users', column: 'email' }),
* ])
* ```
*/
/*
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
*
* For example:
* 1. The username must be of data type string. But then also, it should
* not contain special characters or numbers.
* ```
* schema.string({}, [ rules.alpha() ])
* ```
*
* 2. The email must be of data type string, formatted as a valid
* email. But also, not used by any other user.
* ```
* schema.string({}, [
* rules.email(),
* rules.unique({ table: 'users', column: 'email' }),
* ])
* ```
*/
// public refs = schema.refs({
// id: this.ctx.params.id
// })
// public refs = schema.refs({
// id: this.ctx.params.id
// })
// public schema = schema.create({
// login: schema.string({ trim: true }, [
// rules.minLength(3),
// rules.maxLength(50),
// rules.unique({
// table: 'accounts',
// column: 'login',
// // whereNot: { id: this.refs.id }
// whereNot: { id: this.ctx?.params.id },
// }),
// // rules.regex(/^[a-zA-Z0-9-_]+$/),
// //Must be alphanumeric with hyphens or underscores
// ]),
// email: schema.string({}, [rules.email(), rules.unique({ table: 'accounts', column: 'email' })]),
// password: schema.string.optional([rules.confirmed(), rules.minLength(6)]),
// roles: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
// });
// public schema = schema.create({
// login: schema.string({ trim: true }, [
// rules.minLength(3),
// rules.maxLength(50),
// rules.unique({
// table: 'accounts',
// column: 'login',
// // whereNot: { id: this.refs.id }
// whereNot: { id: this.ctx?.params.id },
// }),
// // rules.regex(/^[a-zA-Z0-9-_]+$/),
// //Must be alphanumeric with hyphens or underscores
// ]),
// email: schema.string({}, [rules.email(), rules.unique({ table: 'accounts', column: 'email' })]),
// password: schema.string.optional([rules.confirmed(), rules.minLength(6)]),
// roles: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
// });
/**
* Custom messages for validation failures. You can make use of dot notation `(.)`
* for targeting nested fields and array expressions `(*)` for targeting all
* children of an array. For example:
*
* {
* 'profile.username.required': 'Username is required',
* 'scores.*.number': 'Define scores as valid numbers'
* }
*
*/
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',
'roles.minLength': 'at least {{ options.minLength }} role must be defined',
'roles.*.number': 'Define roles as valid numbers',
};
/**
* Custom messages for validation failures. You can make use of dot notation `(.)`
* for targeting nested fields and array expressions `(*)` for targeting all
* children of an array. For example:
*
* {
* 'profile.username.required': 'Username is required',
* 'scores.*.number': 'Define scores as valid numbers'
* }
*
*/
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',
'roles.minLength': 'at least {{ options.minLength }} role must be defined',
'roles.*.number': 'Define roles as valid numbers',
};
}

View File

@ -1,6 +1,8 @@
import { BaseCommand } from '@adonisjs/core/build/standalone';
import crypto from 'crypto';
import fs from 'fs';
// import Config from '@ioc:Adonis/Core/Config';
import Logger from '@ioc:Adonis/Core/Logger'
export default class ValidateChecksum extends BaseCommand {
/**
@ -37,6 +39,9 @@ export default class ValidateChecksum extends BaseCommand {
// query all files from database:
const files = await File.query().preload('hashvalues');
// const logLevel = Config.get('app.logger.level', 'info');
// console.log(this.logger.)
for (var file of files) {
let hashValue = await file.related('hashvalues').query().pluck('value', 'type');
@ -45,16 +50,17 @@ export default class ValidateChecksum extends BaseCommand {
try {
calculatedMd5FileHash = await this.checksumFile(filePath, 'md5');
} catch (exception) {
this.logger.error(exception.message);
// this.logger.error(exception.message);
Logger.error(exception.message);
continue;
}
if (hashValue['md5'] == calculatedMd5FileHash) {
this.logger.info(
Logger.info(
`File id ${file.id}: stored md5 checksum: ${calculatedMd5FileHash}, control md5 checksum: ${hashValue['md5']}`,
);
} else {
this.logger.logError(
Logger.error(
`File id ${file.id}: stored md5 checksum: ${calculatedMd5FileHash}, control md5 checksum: ${hashValue['md5']}`,
);
}

View File

@ -154,6 +154,10 @@ export const logger: LoggerConfig = {
|
*/
level: Env.get('LOG_LEVEL', 'info'),
redact: {
paths: ['password', '*.password'],
},
/*
|--------------------------------------------------------------------------

View File

@ -1,25 +1,25 @@
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
import Config from '@ioc:Adonis/Core/Config'
import BaseSchema from '@ioc:Adonis/Lucid/Schema';
import Config from '@ioc:Adonis/Core/Config';
export default class Roles extends BaseSchema {
protected tableName = Config.get('rolePermission.role_table', 'roles')
protected tableName = Config.get('rolePermission.role_table', 'roles');
public async up () {
this.schema.createTable(this.tableName, (table) => {
table.increments('id')
table.string('name', 191).unique()
table.string('slug', 191).nullable().unique()
table.string('description', 191).nullable()
public async up() {
this.schema.createTable(this.tableName, (table) => {
table.increments('id');
table.string('name', 191).unique();
table.string('slug', 191).nullable().unique();
table.string('description', 191).nullable();
/**
* Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
*/
table.timestamp('created_at', { useTz: true }).nullable()
table.timestamp('updated_at', { useTz: true }).nullable()
})
}
/**
* Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
*/
table.timestamp('created_at', { useTz: true }).nullable();
table.timestamp('updated_at', { useTz: true }).nullable();
});
}
public async down () {
this.schema.dropTable(this.tableName)
}
public async down() {
this.schema.dropTable(this.tableName);
}
}

32
env.ts
View File

@ -12,21 +12,21 @@
|
*/
import Env from '@ioc:Adonis/Core/Env'
import Env from '@ioc:Adonis/Core/Env';
export default Env.rules({
HOST: Env.schema.string({ format: 'host' }),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
APP_NAME: Env.schema.string(),
CACHE_VIEWS: Env.schema.boolean(),
SESSION_DRIVER: Env.schema.string(),
DRIVE_DISK: Env.schema.enum(['local'] as const),
NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
DB_CONNECTION: Env.schema.string(),
PG_HOST: Env.schema.string({ format: 'host' }),
PG_PORT: Env.schema.number(),
PG_USER: Env.schema.string(),
PG_PASSWORD: Env.schema.string.optional(),
PG_DB_NAME: Env.schema.string(),
})
HOST: Env.schema.string({ format: 'host' }),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
APP_NAME: Env.schema.string(),
CACHE_VIEWS: Env.schema.boolean(),
SESSION_DRIVER: Env.schema.string(),
DRIVE_DISK: Env.schema.enum(['local'] as const),
NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
DB_CONNECTION: Env.schema.string(),
PG_HOST: Env.schema.string({ format: 'host' }),
PG_PORT: Env.schema.number(),
PG_USER: Env.schema.string(),
PG_PASSWORD: Env.schema.string.optional(),
PG_DB_NAME: Env.schema.string(),
});

625
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,17 +28,6 @@
"eslintIgnore": [
"build"
],
"prettier": {
"trailingComma": "all",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"useTabs": false,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 120
},
"alias": {
"vue": "./node_modules/vue/dist/vue.esm-bundler.js"
},
@ -71,7 +60,7 @@
"naive-ui": "^2.34.3",
"numeral": "^2.0.6",
"pinia": "^2.0.30",
"pino-pretty": "^9.1.1",
"pino-pretty": "^10.0.0",
"postcss-loader": "^7.0.2",
"prettier": "^2.8.3",
"tailwindcss": "^3.2.4",
@ -91,7 +80,9 @@
"@adonisjs/shield": "^7.1.0",
"@adonisjs/view": "^6.1.5",
"@eidellev/adonis-stardust": "^3.0.0",
"@eidellev/inertia-adonisjs": "^7.4.0",
"@eidellev/inertia-adonisjs": "^8.0.0",
"@fontsource/archivo-black": "^4.5.9",
"@fontsource/inter": "^4.5.15",
"@inertiajs/inertia": "^0.11.1",
"@inertiajs/vue3": "^1.0.0",
"bcryptjs": "^2.4.3",

View File

@ -1,4 +1,15 @@
{
"assets/app.css": "http://localhost:8080/assets/app.css",
"assets/app.js": "http://localhost:8080/assets/app.js"
"assets/app.js": "http://localhost:8080/assets/app.js",
"assets/fonts/inter-all-400-normal.woff": "http://localhost:8080/assets/fonts/inter-all-400-normal.8c804432.woff",
"assets/fonts/archivo-black-all-400-normal.woff": "http://localhost:8080/assets/fonts/archivo-black-all-400-normal.da68e413.woff",
"assets/fonts/inter-latin-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-latin-ext-400-normal.3a7a7652.woff2",
"assets/fonts/archivo-black-latin-400-normal.woff2": "http://localhost:8080/assets/fonts/archivo-black-latin-400-normal.fc847a1f.woff2",
"assets/fonts/inter-latin-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-latin-400-normal.be7cb18d.woff2",
"assets/fonts/archivo-black-latin-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/archivo-black-latin-ext-400-normal.21761451.woff2",
"assets/fonts/inter-cyrillic-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-cyrillic-ext-400-normal.fcc125c4.woff2",
"assets/fonts/inter-greek-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-greek-400-normal.0278a49f.woff2",
"assets/fonts/inter-cyrillic-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-cyrillic-400-normal.8684fef6.woff2",
"assets/fonts/inter-greek-ext-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-greek-ext-400-normal.3f642a92.woff2",
"assets/fonts/inter-vietnamese-400-normal.woff2": "http://localhost:8080/assets/fonts/inter-vietnamese-400-normal.789afb71.woff2"
}

View File

@ -1,30 +1,44 @@
/* @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&display=swap'); */
@import url('https://fonts.googleapis.com/css?family=Roboto:400,400i,600,700');
/* @import url('https://fonts.googleapis.com/css?family=Roboto:400,400i,600,700'); */
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "_checkbox-radio-switch.css";
@import "_progress.css";
@import "_scrollbars.css";
@import "_table.css";
@import '_checkbox-radio-switch.css';
@import '_progress.css';
@import '_scrollbars.css';
@import '_table.css';
@import '@fontsource/inter/index.css';
@import '@fontsource/archivo-black/index.css';
html, body {
background-color: #F7F8FA;
font-family: 'Roboto', sans-serif;
/* height: 100vh; */
color: #46444c;
position: relative;
}
/* @layer base {
html,
body {
background-color: #f7f8fa;
font-family: Inter, system-ui, sans-serif;
height: 100vh;
color: #46444c;
position: relative;
}
} */
/* html,
body {
background-color: #f7f8fa;
font-family: 'Roboto', sans-serif;
height: 100vh;
color: #46444c;
position: relative;
} */
.px-6 {
padding-left: 0;
/* padding-right: 1.5rem; */
padding-left: 0;
/* padding-right: 1.5rem; */
}
.rounded-md {
color: gray;
color: gray;
}
/* body:before {
@ -90,6 +104,6 @@ main li:before {
} */
main code {
font-size: 16px;
background: #e6e2ff;
font-size: 16px;
background: #e6e2ff;
}

View File

@ -2,8 +2,8 @@
// import { reactive, computed } from 'vue';
// import { usePage } from '@inertiajs/vue3'
// import { usePage } from '@inertiajs/inertia-vue3';
import { LayoutService } from '@/Stores/layout.js';
import menu from '@/menu.js'
import { LayoutService } from '@/Stores/layout';
import menu from '@/menu'
import AsideMenuLayer from '@/Components/AsideMenuLayer.vue';
import OverlayLayer from '@/Components/OverlayLayer.vue';

View File

@ -3,7 +3,7 @@ import { ref, computed } from 'vue';
import { Link } from '@inertiajs/vue3';
// import { Link } from '@inertiajs/inertia-vue3';
import { StyleService } from '@/Stores/style.js';
import { StyleService } from '@/Stores/style';
import { mdiMinus, mdiPlus } from '@mdi/js';
import { getButtonColor } from '@/colors.js';
import BaseIcon from '@/Components/BaseIcon.vue';

View File

@ -4,8 +4,8 @@ import { router } from '@inertiajs/vue3'
import { stardust } from '@eidellev/adonis-stardust/client';
import { mdiLogout, mdiClose } from '@mdi/js';
import { computed } from 'vue';
import { LayoutService } from '@/Stores/layout.js';
import { StyleService } from '@/Stores/style.js';
import { LayoutService } from '@/Stores/layout';
import { StyleService } from '@/Stores/style';
import AsideMenuList from '@/Components/AsideMenuList.vue';
import AsideMenuItem from '@/Components/AsideMenuItem.vue';
import BaseIcon from '@/Components/BaseIcon.vue';

View File

@ -2,28 +2,28 @@
import AsideMenuItem from '@/Components/AsideMenuItem.vue';
defineProps({
isDropdownList: Boolean,
menu: {
type: Object,
default: () => {}
}
})
isDropdownList: Boolean,
menu: {
type: Object,
default: () => {},
},
});
const emit = defineEmits(['menu-click'])
const emit = defineEmits(['menu-click']);
const menuClick = (event, item) => {
emit('menu-click', event, item)
}
emit('menu-click', event, item);
};
</script>
<template>
<ul>
<AsideMenuItem
v-for="(item, index) in menu"
:key="index"
v-bind:item="item"
:is-dropdown-list="isDropdownList"
@menu-click="menuClick"
/>
</ul>
<ul>
<AsideMenuItem
v-for="(item, index) in menu"
:key="index"
v-bind:item="item"
:is-dropdown-list="isDropdownList"
@menu-click="menuClick"
/>
</ul>
</template>

View File

@ -1,111 +1,81 @@
<script setup>
import { mdiCog } from '@mdi/js'
import { computed, useSlots } from 'vue'
import BaseIcon from '@/Components/BaseIcon.vue'
import { mdiCog } from '@mdi/js';
import { computed, useSlots } from 'vue';
import BaseIcon from '@/Components/BaseIcon.vue';
const props = defineProps({
title: {
type: String,
default: null
},
icon: {
type: String,
default: null
},
headerIcon: {
type: String,
default: null
},
rounded: {
type: String,
default: 'rounded-xl'
},
hasTable: Boolean,
empty: Boolean,
form: Boolean,
hoverable: Boolean,
modal: Boolean
})
title: {
type: String,
default: null,
},
icon: {
type: String,
default: null,
},
headerIcon: {
type: String,
default: null,
},
rounded: {
type: String,
default: 'rounded-xl',
},
hasFormData: Boolean,
empty: Boolean,
form: Boolean,
hoverable: Boolean,
modal: Boolean,
});
const emit = defineEmits(['header-icon-click', 'submit'])
const emit = defineEmits(['header-icon-click', 'submit']);
const is = computed(() => props.form ? 'form' : 'div')
const is = computed(() => (props.form ? 'form' : 'div'));
const slots = useSlots()
const slots = useSlots();
const footer = computed(() => slots.footer && !!slots.footer())
const footer = computed(() => slots.footer && !!slots.footer());
const componentClass = computed(() => {
const base = [
props.rounded,
props.modal ? 'dark:bg-slate-900' : 'dark:bg-slate-900/70'
]
const base = [props.rounded, props.modal ? 'dark:bg-slate-900' : 'dark:bg-slate-900/70'];
if (props.hoverable) {
base.push('hover:shadow-lg transition-shadow duration-500')
}
if (props.hoverable) {
base.push('hover:shadow-lg transition-shadow duration-500');
}
return base
})
return base;
});
const computedHeaderIcon = computed(() => props.headerIcon ?? mdiCog)
const computedHeaderIcon = computed(() => props.headerIcon ?? mdiCog);
const headerIconClick = () => {
emit('header-icon-click')
}
emit('header-icon-click');
};
const submit = e => {
emit('submit', e)
}
const submit = (e) => {
emit('submit', e);
};
</script>
<template>
<component
:is="is"
:class="componentClass"
class="bg-white flex flex-col"
@submit="submit"
>
<header
v-if="title"
class="flex items-stretch border-b border-gray-100 dark:border-slate-800"
>
<div
class="flex items-center py-3 grow font-bold"
:class="[ icon ? 'px-4' : 'px-6' ]"
>
<BaseIcon
v-if="icon"
:path="icon"
class="mr-3"
/>
{{ title }}
</div>
<button
class="flex items-center py-3 px-4 justify-center ring-blue-700 focus:ring"
@click="headerIconClick"
>
<BaseIcon :path="computedHeaderIcon" />
</button>
</header>
<div
v-if="empty"
class="text-center py-24 text-gray-500 dark:text-slate-400"
>
<p>Nothing's here</p>
</div>
<div
v-else
class="flex-1"
:class="{'p-6':!hasTable}"
>
<slot />
</div>
<div
v-if="footer"
class="p-6 border-t border-gray-100 dark:border-slate-800"
>
<slot name="footer" />
</div>
</component>
<component :is="is" :class="componentClass" class="bg-white flex flex-col" @submit="submit">
<header v-if="title" class="flex items-stretch border-b border-gray-100 dark:border-slate-800">
<div class="flex items-center py-3 grow font-bold" :class="[icon ? 'px-4' : 'px-6']">
<BaseIcon v-if="icon" :path="icon" class="mr-3" />
{{ title }}
</div>
<button class="flex items-center py-3 px-4 justify-center ring-blue-700 focus:ring" @click="headerIconClick">
<BaseIcon :path="computedHeaderIcon" />
</button>
</header>
<div v-if="empty" class="text-center py-24 text-gray-500 dark:text-slate-400">
<p>Nothing's here</p>
</div>
<!-- <div v-else class="flex-1" :class="{'p-6':!hasTable}"> -->
<div v-else class="flex-1" :class="[!hasFormData && 'p-6']">
<slot />
</div>
<div v-if="footer" class="p-6 border-t border-gray-100 dark:border-slate-800">
<slot name="footer" />
</div>
</component>
</template>

View File

@ -1,56 +1,62 @@
<script setup>
import { computed } from 'vue'
import { mdiClose } from '@mdi/js'
import BaseButton from '@/Components/BaseButton.vue'
import BaseButtons from '@/Components/BaseButtons.vue'
import CardBox from '@/Components/CardBox.vue'
import OverlayLayer from '@/Components/OverlayLayer.vue'
<script setup lang="ts">
import { computed } from 'vue';
import { mdiClose } from '@mdi/js';
import BaseButton from '@/Components/BaseButton.vue';
import BaseButtons from '@/Components/BaseButtons.vue';
import CardBox from '@/Components/CardBox.vue';
import OverlayLayer from '@/Components/OverlayLayer.vue';
const props = defineProps({
title: {
type: String,
default: null
default: null,
},
largeTitle: {
type: String,
default: null
default: null,
},
button: {
type: String,
default: 'info'
default: 'info',
},
buttonLabel: {
type: String,
default: 'Done'
default: 'Done',
},
hasCancel: Boolean,
modelValue: {
type: [String, Number, Boolean],
default: null
}
})
default: null,
},
});
const emit = defineEmits(['update:modelValue', 'cancel', 'confirm'])
const emit = defineEmits(['update:modelValue', 'cancel', 'confirm']);
const value = computed({
get: () => props.modelValue,
set: value => emit('update:modelValue', value)
})
set: (value) => emit('update:modelValue', value),
});
const confirmCancel = (mode) => {
value.value = false;
emit(mode);
}
};
const confirm = () => confirmCancel('confirm')
const confirm = () => confirmCancel('confirm');
const cancel = () => confirmCancel('cancel')
const cancel = () => confirmCancel('cancel');
</script>
<template>
<OverlayLayer v-show="value" @overlay-click="cancel">
<CardBox v-show="value" :title="title" class="shadow-lg max-h-modal w-11/12 md:w-3/5 lg:w-2/5 xl:w-4/12 z-50"
:header-icon="mdiClose" modal @header-icon-click="cancel">
<CardBox
v-show="value"
:title="title"
class="shadow-lg max-h-modal w-11/12 md:w-3/5 lg:w-2/5 xl:w-4/12 z-50"
:header-icon="mdiClose"
modal
@header-icon-click="cancel"
>
<div class="space-y-3">
<h1 v-if="largeTitle" class="text-2xl">
{{ largeTitle }}

View File

@ -3,66 +3,71 @@ import { computed, ref, onMounted, onBeforeUnmount } from 'vue';
import { MainService } from '@/Stores/main';
import FormControlIcon from '@/Components/FormControlIcon.vue';
const props = defineProps({
name: {
type: String,
default: null,
},
id: {
type: String,
default: null,
},
autocomplete: {
type: String,
default: null,
},
placeholder: {
type: String,
default: null,
},
inputmode: {
type: String,
default: null,
},
icon: {
type: String,
default: null,
},
options: {
type: Array,
default: null,
},
type: {
type: String,
default: 'text',
},
modelValue: {
type: [String, Number, Boolean, Array, Object],
default: '',
},
required: Boolean,
borderless: Boolean,
transparent: Boolean,
ctrlKFocus: Boolean,
name: {
type: String,
default: null,
},
id: {
type: String,
default: null,
},
autocomplete: {
type: String,
default: null,
},
placeholder: {
type: String,
default: null,
},
inputmode: {
type: String,
default: null,
},
icon: {
type: String,
default: null,
},
options: {
type: [Array, Object],
default: null,
},
type: {
type: String,
default: 'text',
},
isReadOnly: {
type: Boolean,
default: false,
},
modelValue: {
type: [String, Number, Boolean, Array, Object],
default: '',
},
required: Boolean,
borderless: Boolean,
transparent: Boolean,
ctrlKFocus: Boolean,
});
const emit = defineEmits(['update:modelValue', 'setRef']);
const computedValue = computed({
get: () => props.modelValue,
set: (value) => {
emit('update:modelValue', value);
},
get: () => props.modelValue,
set: (value) => {
emit('update:modelValue', value);
},
});
const inputElClass = computed(() => {
const base = [
'px-3 py-2 max-w-full focus:ring focus:outline-none border-gray-700 rounded w-full',
'dark:placeholder-gray-400',
computedType.value === 'textarea' ? 'h-24' : 'h-12',
props.borderless ? 'border-0' : 'border',
props.transparent ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
];
if (props.icon) {
base.push('pl-10');
}
return base;
const base = [
'px-3 py-2 max-w-full focus:ring focus:outline-none border-gray-700 rounded w-full',
'dark:placeholder-gray-400',
computedType.value === 'textarea' ? 'h-24' : 'h-12',
props.borderless ? 'border-0' : 'border',
// props.transparent && !props.isReadOnly ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
props.isReadOnly ? 'bg-gray-50 dark:bg-slate-600' : 'bg-white dark:bg-slate-800',
];
if (props.icon) {
base.push('pl-10');
}
return base;
});
const computedType = computed(() => (props.options ? 'select' : props.type));
const controlIconH = computed(() => (props.type === 'textarea' ? 'h-full' : 'h-12'));
@ -71,74 +76,70 @@ const selectEl = ref(null);
const textareaEl = ref(null);
const inputEl = ref(null);
onMounted(() => {
if (selectEl.value) {
emit('setRef', selectEl.value);
} else if (textareaEl.value) {
emit('setRef', textareaEl.value);
} else {
emit('setRef', inputEl.value);
}
if (selectEl.value) {
emit('setRef', selectEl.value);
} else if (textareaEl.value) {
emit('setRef', textareaEl.value);
} else {
emit('setRef', inputEl.value);
}
});
if (props.ctrlKFocus) {
const fieldFocusHook = (e) => {
if (e.ctrlKey && e.key === 'k') {
e.preventDefault();
inputEl.value.focus();
} else if (e.key === 'Escape') {
inputEl.value.blur();
}
};
onMounted(() => {
if (!mainService.isFieldFocusRegistered) {
window.addEventListener('keydown', fieldFocusHook);
mainService.isFieldFocusRegistered = true;
} else {
// console.error('Duplicate field focus event')
}
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', fieldFocusHook);
mainService.isFieldFocusRegistered = false;
});
const fieldFocusHook = (e) => {
if (e.ctrlKey && e.key === 'k') {
e.preventDefault();
inputEl.value.focus();
} else if (e.key === 'Escape') {
inputEl.value.blur();
}
};
onMounted(() => {
if (!mainService.isFieldFocusRegistered) {
window.addEventListener('keydown', fieldFocusHook);
mainService.isFieldFocusRegistered = true;
} else {
// console.error('Duplicate field focus event')
}
});
onBeforeUnmount(() => {
window.removeEventListener('keydown', fieldFocusHook);
mainService.isFieldFocusRegistered = false;
});
}
</script>
<template>
<div class="relative">
<select
v-if="computedType === 'select'"
:id="id"
v-model="computedValue"
:name="name"
:class="inputElClass"
>
<option v-for="option in options" :key="option.id ?? option" :value="option">
{{ option.label ?? option }}
</option>
</select>
<textarea
v-else-if="computedType === 'textarea'"
:id="id"
v-model="computedValue"
:class="inputElClass"
:name="name"
:placeholder="placeholder"
:required="required"
/>
<input
v-else
:id="id"
ref="inputEl"
v-model="computedValue"
:name="name"
:inputmode="inputmode"
:autocomplete="autocomplete"
:required="required"
:placeholder="placeholder"
:type="computedType"
:class="inputElClass"
/>
<FormControlIcon v-if="icon" :icon="icon" :h="controlIconH" />
<slot />
</div>
<div class="relative">
<select v-if="computedType === 'select'" :id="id" v-model="computedValue" :name="name" :class="inputElClass">
<option v-if="placeholder" class="text-opacity-25" value="" disabled selected>{{ placeholder }}</option>
<option v-for="option in options" :key="option.id ?? option" :value="option.value ?? option">
{{ option.label ?? option }}
</option>
</select>
<textarea
v-else-if="computedType === 'textarea'"
:id="id"
v-model="computedValue"
:class="inputElClass"
:name="name"
:placeholder="placeholder"
:required="required"
/>
<input
v-else
:id="id"
ref="inputEl"
v-model="computedValue"
:name="name"
:inputmode="inputmode"
:autocomplete="autocomplete"
:required="required"
:placeholder="placeholder"
:type="computedType"
:class="inputElClass"
:readonly="isReadOnly"
/>
<FormControlIcon v-if="icon" :icon="icon" :h="controlIconH" />
<slot />
</div>
</template>

View File

@ -2,46 +2,47 @@
import { computed, useSlots } from 'vue';
defineProps({
label: {
type: String,
default: null,
},
labelFor: {
type: String,
default: null,
},
help: {
type: String,
default: null,
},
label: {
type: String,
default: null,
},
labelFor: {
type: String,
default: null,
},
help: {
type: String,
default: null,
},
});
const slots = useSlots();
const wrapperClass = computed(() => {
const base = [];
const slotsLength = slots.default().length;
const base = [];
const slotsLength = slots.default().length;
if (slotsLength > 1) {
base.push('grid grid-cols-1 gap-3');
}
if (slotsLength > 1) {
base.push('grid grid-cols-1 gap-3');
}
if (slotsLength === 2) {
base.push('md:grid-cols-2');
}
if (slotsLength === 2) {
base.push('md:grid-cols-2');
}
return base;
return base;
});
</script>
<template>
<div class="mb-6 last:mb-0">
<label v-if="label" :for="labelFor" class="block font-bold mb-2">{{ label }}</label>
<div v-bind:class="wrapperClass">
<slot />
</div>
<div v-if="help" class="text-xs text-gray-500 dark:text-slate-400 mt-1">
{{ help }}
</div>
</div>
<div class="mb-6 last:mb-0">
<!-- <label v-if="label" :for="labelFor" class="block font-bold mb-2">{{ label }}</label> -->
<label v-if="label" :for="labelFor" class="font-bold h-6 mt-3 text-xs leading-8 uppercase">{{ label }}</label>
<div v-bind:class="wrapperClass">
<slot />
</div>
<div v-if="help" class="text-xs text-gray-500 dark:text-slate-400 mt-1">
{{ help }}
</div>
</div>
</template>

View File

@ -0,0 +1,44 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="my-svg-component"
>
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
</svg>
</template>
<script lang="ts">
export default {
name: 'Icon_Mandatory',
};
</script>
<style scoped>
.my-svg-component {
/* Scoped CSS here */
width: 100%;
height: 100%;
fill: none;
stroke: currentColor;
stroke-width: 1;
stroke-linecap: round;
stroke-linejoin: round;
}
/* path,
circle {
stroke: #ffffff;
stroke-width: 6px;
fill: none;
} */
</style>

View File

@ -0,0 +1,35 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="my-svg-component">
<path
fill-rule="evenodd"
d="M9 2.25a.75.75 0 01.75.75v1.506a49.38 49.38 0 015.343.371.75.75 0 11-.186 1.489c-.66-.083-1.323-.151-1.99-.206a18.67 18.67 0 01-2.969 6.323c.317.384.65.753.998 1.107a.75.75 0 11-1.07 1.052A18.902 18.902 0 019 13.687a18.823 18.823 0 01-5.656 4.482.75.75 0 11-.688-1.333 17.323 17.323 0 005.396-4.353A18.72 18.72 0 015.89 8.598a.75.75 0 011.388-.568A17.21 17.21 0 009 11.224a17.17 17.17 0 002.391-5.165 48.038 48.038 0 00-8.298.307.75.75 0 01-.186-1.489 49.159 49.159 0 015.343-.371V3A.75.75 0 019 2.25zM15.75 9a.75.75 0 01.68.433l5.25 11.25a.75.75 0 01-1.36.634l-1.198-2.567h-6.744l-1.198 2.567a.75.75 0 01-1.36-.634l5.25-11.25A.75.75 0 0115.75 9zm-2.672 8.25h5.344l-2.672-5.726-2.672 5.726z"
clip-rule="evenodd"
/>
</svg>
</template>
<script>
export default {
name: 'Icon_Mandatory',
};
</script>
<style scoped>
.my-svg-component {
/* Scoped CSS here */
width: 100%;
height: 100%;
fill: none;
stroke: currentColor;
stroke-width: 1;
stroke-linecap: round;
stroke-linejoin: round;
}
/* path,
circle {
stroke: #ffffff;
stroke-width: 6px;
fill: none;
} */
</style>

View File

@ -0,0 +1,35 @@
<template>
<svg class="my-svg-component" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z"
/>
</svg>
</template>
<script>
export default {
name: 'Icon_Mandatory',
};
</script>
<style scoped>
.my-svg-component {
/* Scoped CSS here */
width: 100%;
height: 100%;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* path,
circle {
stroke: #ffffff;
stroke-width: 6px;
fill: none;
} */
</style>

View File

@ -0,0 +1,33 @@
<template>
<svg class="my-svg-component" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M6 3a3 3 0 00-3 3v2.25a3 3 0 003 3h2.25a3 3 0 003-3V6a3 3 0 00-3-3H6zM15.75 3a3 3 0 00-3 3v2.25a3 3 0 003 3H18a3 3 0 003-3V6a3 3 0 00-3-3h-2.25zM6 12.75a3 3 0 00-3 3V18a3 3 0 003 3h2.25a3 3 0 003-3v-2.25a3 3 0 00-3-3H6zM17.625 13.5a.75.75 0 00-1.5 0v2.625H13.5a.75.75 0 000 1.5h2.625v2.625a.75.75 0 001.5 0v-2.625h2.625a.75.75 0 000-1.5h-2.625V13.5z"
/>
</svg>
</template>
<script>
export default {
name: 'Icon_Mandatory',
};
</script>
<style scoped>
.my-svg-component {
/* Scoped CSS here */
width: 100%;
height: 100%;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* path,
circle {
stroke: #ffffff;
stroke-width: 6px;
fill: none;
} */
</style>

View File

@ -0,0 +1,49 @@
<template>
<div class="flex items-center relative">
<!-- v-bind:class="{ 'text-white bg-teal-600 border-teal-600': isCurrent, 'border-teal-600': isChecked }" -->
<div
class="text-gray-500 rounded-full transition duration-500 ease-in-out h-12 w-12 py-3 border-2"
:class="[
isCurrent ? 'text-white bg-teal-600 border-teal-600' : 'border-gray-300',
isChecked && 'text-teal-600 border-teal-600',
]"
>
<!-- <svg class="my-svg-component" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z"
/>
</svg> -->
<slot></slot>
<div class="absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase invisible sm:visible">
{{ label }}
</div>
</div>
</div>
<div v-if="!isLastStep"
class="flex-auto border-t-2 transition duration-500 ease-in-out invisible sm:visible"
:class="[isChecked ? 'border-teal-600' : 'border-gray-300']"
></div>
</template>
<script>
export default {
name: 'Icon_Multistep',
props: {
isCurrent: Boolean,
isChecked: Boolean,
isLastStep: Boolean,
label: String,
},
data() {
return {
mode: 'light',
checkedClass: 'border-teal-600',
uncheckedClass: 'border-gray-300',
};
},
};
</script>

View File

@ -7,8 +7,8 @@ import {ComputedRef} from "vue";
import { computed, ref } from 'vue';
import { containerMaxW } from '@/config.js';
// import { MainService } from '@/Stores/main.js';
import { StyleService } from '@/Stores/style.js';
import { LayoutService } from '@/Stores/layout.js';
import { StyleService } from '@/Stores/style';
import { LayoutService } from '@/Stores/layout';
import {
mdiForwardburger,
mdiBackburger,

View File

@ -1,5 +1,5 @@
<script setup>
import { StyleService } from '@/Stores/style.js'
import { StyleService } from '@/Stores/style'
// import { Link } from '@inertiajs/vue3'
import { Link } from '@inertiajs/vue3'
import { computed } from 'vue'

View File

@ -1,5 +1,5 @@
<script setup>
import { StyleService } from '@/Stores/style.js'
import { StyleService } from '@/Stores/style'
import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import NavBarItem from '@/Components/NavBarItem.vue'

View File

@ -1,5 +1,5 @@
<script setup>
import { StyleService } from '@/Stores/style.js';
import { StyleService } from '@/Stores/style';
defineProps({
zIndex: {

View File

@ -0,0 +1,281 @@
<template>
<!-- <input
v-model="data.search"
@change="onChange"
type="text"
class="text-base font-medium block w-full rounded-md border transition ease-in-out focus:ring-1 border-gray-300 border-solid py-2 px-3 text-gray-700 placeholder-gray-400 focus:border-blue-200 focus:ring-blue-500 focus:outline-none"
v-bind:name="props.name"
/>
<ul v-if="data.isOpen" class="mt-1 border-2 border-slate-50 overflow-auto shadow-lg rounded list-none">
<li
:class="['hover:bg-blue-100 hover:text-blue-800', 'w-full list-none text-left py-2 px-3 cursor-pointer']"
v-for="(result, i) in data.results"
:key="i"
>
{{ result.name }}
</li>
</ul> -->
<!-- <div class="flex-col justify-center relative"> -->
<div class="relative">
<input
v-model="data.search"
type="text"
:class="inputElClass"
:name="props.name"
:placeholder="placeholder"
autocomplete="off"
@keydown.down="onArrowDown"
@keydown.up="onArrowUp"
/>
<svg
class="w-4 h-4 absolute left-2.5 top-3.5"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<!-- <ul v-if="data.isOpen" class="bg-white border border-gray-100 w-full mt-2 max-h-28 overflow-y-auto"> -->
<!-- :ref="(el) => { ul[i] = el }" -->
<ul v-if="data.isOpen" class="bg-white dark:bg-slate-800 w-full mt-2 max-h-28 overflow-y-auto scroll-smooth">
<li
class="pl-8 pr-2 py-1 border-b-2 border-gray-100 relative cursor-pointer hover:bg-yellow-50 hover:text-gray-900"
:class="{
'bg-yellow-50 text-gray-900': i == selectedIndex,
}"
v-for="(result, i) in data.results"
:key="i"
:ref="
(el: HTMLLIElement | null) => {
ul[i] = el;
}
"
>
<svg class="absolute w-4 h-4 left-2 top-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path
fill-rule="evenodd"
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
<!-- <b>Gar</b>{{ result.name }} -->
<!-- <span>
{{ makeBold(result.name) }}
</span> -->
<span
v-for="(item, index) in makeBold(result.name)"
:key="index"
:class="{
'font-bold': data.search.toLowerCase().includes(item.toLowerCase()),
'is-active': index == selectedIndex,
}"
>
{{ item }}
</span>
</li>
</ul>
<!-- </div> -->
</template>
<script setup lang="ts">
import { reactive, ref, computed, Ref, watch } from 'vue';
import axios from 'axios';
let props = defineProps({
name: {
type: String,
required: false,
default: 'autocomplete',
},
source: {
type: [String, Array, Function],
required: true,
default: '',
},
label: {
type: String,
required: false,
default: 'name',
},
responseProperty: {
type: String,
required: false,
default: 'name',
},
placeholder: {
type: String,
default: null,
},
icon: {
type: String,
default: null,
},
required: Boolean,
borderless: Boolean,
transparent: Boolean,
ctrlKFocus: Boolean,
});
const inputElClass = computed(() => {
const base = [
'px-3 py-2 max-w-full focus:ring focus:outline-none border-gray-700 rounded w-full',
'dark:placeholder-gray-400',
'h-12',
props.borderless ? 'border-0' : 'border',
props.transparent ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
// props.isReadOnly ? 'bg-gray-50 dark:bg-slate-600' : 'bg-white dark:bg-slate-800',
];
// if (props.icon) {
base.push('pl-10');
// }
return base;
});
let search = ref('');
let data = reactive({
search: search,
isOpen: false,
results: [],
});
let error = ref('');
let selectedIndex: Ref<number> = ref(0);
// const listItem = ref(null);
const ul: Ref<Array<HTMLLIElement | null>> = ref([]);
watch(selectedIndex, (selectedIndex) => {
if (selectedIndex != null && ul.value != null) {
const currentElement: HTMLLIElement | null = ul.value[selectedIndex];
currentElement &&
currentElement.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'start',
});
}
});
watch(search, async () => {
await onChange();
});
// function clear() {
// data.search = "";
// data.isOpen = false;
// data.results = [];
// error.value = "";
// // this.$emit("clear");
// }
// function onChange() {
// if (!props.source || !data.search) {
// return false;
// }
// data.isOpen = true;
// arrayLikeSearch(props.source);
// }
async function onChange() {
if (!props.source || !data.search) return false;
selectedIndex.value = 0;
if (data.search.length >= 2) {
data.isOpen = true;
switch (true) {
case typeof props.source === 'string':
return await request(props.source, data.search);
// case typeof props.source === 'function':
// return props.source(data.search).then((response) => {
// data.results = getResults(response);
// });
case Array.isArray(props.source):
return arrayLikeSearch(props.source);
default:
throw new Error('typeof source is ' + typeof props.source);
}
} else {
data.results = [];
data.isOpen = false;
}
}
function getResults(response) {
// if (props.responseProperty) {
// let foundObj;
// JSON.stringify(response, (_, nestedValue) => {
// if (nestedValue && nestedValue[props.responseProperty]) foundObj = nestedValue[props.responseProperty];
// return nestedValue;
// });
// return foundObj;
// }
if (Array.isArray(response)) {
return response;
}
return [];
}
// function setResult(result) {
// data.search = result[props.label];
// data.isOpen = false;
// }
// function request(url) {
// return axios.get(url).then((response) => {
// data.results = getResults(response);
// });
// }
async function request(url, param) {
try {
let response = await searchTerm(url, param);
error.value = '';
data.results = getResults(response);
// this.results = res.data;
// this.loading = false;
} catch (error) {
error.value = error.message;
// this.loading = false;
}
}
async function searchTerm(term: string, param): Promise<any> {
let res = await axios.get(term, { params: { filter: param } });
// console.log(res.data);
return res.data; //.response;//.docs;
}
// async function request(term: string): Promise<any> {
// let res = await axios.get('/api/persons', { params: { filter: term } });
// return res.data; //.response;//.docs;
// }
function arrayLikeSearch(items) {
data.results = items.filter((item) => {
return item.toLowerCase().indexOf(data.search.toLowerCase()) > -1;
});
}
function makeBold(suggestion) {
const query = data.search.valueOf();
const regex = new RegExp(query.split('').join('-?'), 'i');
const test = suggestion.replace(regex, (match) => '<split>' + match + '<split>');
// return suggestion.match(regex);
// const splitWord = suggestion.match(regex);
return test.split('<split>');
}
function onArrowDown() {
if (data.results.length > 0) {
selectedIndex.value = selectedIndex.value === data.results.length - 1 ? 0 : selectedIndex.value + 1;
// const currentElement: HTMLLIElement = ul.value[selectedIndex.value];
}
}
function onArrowUp() {
if (data.results.length > 0) {
selectedIndex.value = selectedIndex.value == 0 || selectedIndex.value == -1 ? data.results.length - 1 : selectedIndex.value - 1;
}
}
</script>

View File

@ -1,9 +1,9 @@
<script setup>
import { containerMaxW } from '@/config.js'
import { containerMaxW } from '@/config.js';
</script>
<template>
<section class="p-6" v-bind:class="containerMaxW">
<slot />
</section>
<section class="p-6" v-bind:class="containerMaxW">
<slot />
</section>
</template>

30
resources/js/Dataset.ts Normal file
View File

@ -0,0 +1,30 @@
import { Ref } from 'vue';
export interface Dataset {
[key: string]: string | Ref<string>| boolean | Array<Title> | (IErrorMessage | undefined);
language: Ref<string>;
// licenses: Array<number>;
rights: boolean;
type: string;
creating_corporation: string;
titles: Array<Title>;
descriptions: Array<Description>;
errors?: IErrorMessage;
// async (user): Promise<void>;
}
export interface Title {
value: string;
type: string;
language: string | Ref<string>;
}
export interface Description {
value: string;
type: string;
language: string | Ref<string>;
}
interface IErrorMessage {
[key: string]: Array<string>;
}

View File

@ -1,13 +1,13 @@
<script lang="ts" setup>
import { LayoutService } from '@/Stores/layout.js'
import { StyleService } from '@/Stores/style'
import NavBar from '@/Components/NavBar.vue'
import AsideMenu from '@/Components/AsideMenu.vue'
import FooterBar from '@/Components/FooterBar.vue'
import { LayoutService } from '@/Stores/layout';
import { StyleService } from '@/Stores/style';
import NavBar from '@/Components/NavBar.vue';
import AsideMenu from '@/Components/AsideMenu.vue';
import FooterBar from '@/Components/FooterBar.vue';
const styleService = StyleService()
const styleService = StyleService();
const layoutService = LayoutService()
const layoutService = LayoutService();
// defineProps({
// user: {
@ -15,19 +15,23 @@ const layoutService = LayoutService()
// default: () => ({}),
// }
// });
</script>
<template>
<div :class="{ 'dark': styleService.darkMode, 'overflow-hidden lg:overflow-visible': layoutService.isAsideMobileExpanded }">
<div
:class="{ 'ml-60 lg:ml-0': layoutService.isAsideMobileExpanded }"
class="pt-14 xl:pl-60 min-h-screen w-screen transition-position lg:w-auto bg-gray-50 dark:bg-slate-800 dark:text-slate-100"
:class="{
'dark': styleService.darkMode,
'overflow-hidden lg:overflow-visible': layoutService.isAsideMobileExpanded,
}"
>
<NavBar :class="{ 'ml-60 lg:ml-0': layoutService.isAsideMobileExpanded }" />
<AsideMenu />
<slot></slot>
<FooterBar />
<div
:class="{ 'ml-60 lg:ml-0': layoutService.isAsideMobileExpanded }"
class="pt-14 xl:pl-60 min-h-screen w-screen transition-position lg:w-auto bg-gray-50 dark:bg-slate-800 dark:text-slate-100"
>
<NavBar :class="{ 'ml-60 lg:ml-0': layoutService.isAsideMobileExpanded }" />
<AsideMenu />
<slot></slot>
<FooterBar />
</div>
</div>
</div>
</template>

View File

@ -1,5 +1,5 @@
<script setup>
import { StyleService } from '@/Stores/style.js';
import { StyleService } from '@/Stores/style';
const styleService = StyleService();
</script>

View File

@ -1,6 +1,6 @@
<script setup>
import { ref, computed } from 'vue';
import { StyleService } from '@/Stores/style.js';
import { StyleService } from '@/Stores/style';
import {
mdiContrastCircle,

View File

@ -1,6 +1,7 @@
<script setup>
<script setup lang="ts">
// import { Head, Link, useForm } from '@inertiajs/inertia-vue3';
import { Head, Link, useForm, router } from '@inertiajs/vue3';
import { useForm } from '@inertiajs/vue3';
// import { reactive } from 'vue';
import {
mdiAccount,
mdiAccountCircle,
@ -9,7 +10,7 @@ import {
mdiAsterisk,
mdiFormTextboxPassword,
mdiArrowLeftBoldOutline,
mdiAlertBoxOutline,
// mdiAlertBoxOutline,
} from '@mdi/js';
import SectionMain from '@/Components/SectionMain.vue';
import CardBox from '@/Components/CardBox.vue';
@ -18,7 +19,7 @@ import FormField from '@/Components/FormField.vue';
import FormControl from '@/Components/FormControl.vue';
import BaseButton from '@/Components/BaseButton.vue';
import BaseButtons from '@/Components/BaseButtons.vue';
import NotificationBar from '@/Components/NotificationBar.vue';
// import NotificationBar from '@/Components/NotificationBar.vue';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
@ -41,16 +42,16 @@ const profileForm = useForm({
email: props.user.email,
});
const profileSubmit = async () => {
await router.post(stardust.route('admin.account.info.store', [props.user.id]), profileForm);
await profileForm.post(stardust.route('admin.account.info.store', [props.user.id]));
};
const passwordForm = useForm({
old_password: null,
new_password: null,
confirm_password: null,
old_password: "",
new_password: "",
confirm_password: "",
});
const passwordSubmit = async () => {
await router.post(stardust.route('admin.account.info.store'), passwordForm, {
await passwordForm.post(stardust.route('admin.account.info.store'), {
preserveScroll: true,
onSuccess: (resp) => {
console.log(resp);
@ -68,9 +69,9 @@ const passwordSubmit = async () => {
rounded-full small />
</SectionTitleLineWithButton>
<NotificationBar v-if="$page.props.flash.message" color="success" :icon="mdiAlertBoxOutline">
<!-- <NotificationBar v-if="$page.props.flash.message" color="success" :icon="mdiAlertBoxOutline">
{{ $page.props.flash.message }}
</NotificationBar>
</NotificationBar> -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">

View File

@ -1,5 +1,5 @@
<script setup>
import { Head, Link, useForm, router } from '@inertiajs/vue3';
<script setup lang="ts">
import { Head, useForm, router } from '@inertiajs/vue3';
import { mdiAccountKey, mdiArrowLeftBoldOutline } from '@mdi/js';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue';
@ -45,11 +45,11 @@ const submit = async () => {
<SectionMain>
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Add user" main>
<BaseButton :route-name="stardust.route('user.index')" :icon="mdiArrowLeftBoldOutline" label="Back"
color="white" rounded-full small />
color="modern" rounded-full small />
</SectionTitleLineWithButton>
<!-- @submit.prevent="form.post(stardust.route('user.store'))" -->
<CardBox form @submit.prevent="submit()">
<FormField label="Login" :class="{ 'text-red-400': errors.name }">
<FormField label="Login" :class="{ 'text-red-400': errors.login }">
<FormControl v-model="form.login" type="text" placeholder="Enter Login" :errors="errors.login">
<div class="text-red-400 text-sm" v-if="errors.login && Array.isArray(errors.login)">
<!-- {{ errors.login }} -->

View File

@ -1,5 +1,5 @@
<script setup>
import { Head, Link, useForm, router } from "@inertiajs/vue3"
<script setup lang="ts">
import { Head, useForm, router } from "@inertiajs/vue3"
import {
mdiAccountKey,
mdiArrowLeftBoldOutline

View File

@ -1,6 +1,7 @@
<script setup>
<script setup lang="ts">
// import { Head, Link, useForm, usePage } from '@inertiajs/inertia-vue3';
import { Head, Link, useForm, usePage } from '@inertiajs/vue3';
import { ComputedRef } from 'vue';
import {
mdiAccountKey,
mdiPlus,
@ -8,7 +9,7 @@ import {
mdiTrashCan,
mdiAlertBoxOutline,
} from '@mdi/js';
import { watch, computed } from 'vue';
import { computed } from 'vue';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
@ -44,7 +45,7 @@ const props = defineProps({
// return props.filters.search;
// });
const flash = computed(() => {
const flash: ComputedRef<any> = computed(() => {
// let test = usePage();
// console.log(test);
return usePage().props.flash;
@ -73,7 +74,7 @@ const destroy = async (id) => {
<SectionMain>
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Tethys Users" main>
<BaseButton v-if="can.create" :route-name="stardust.route('user.create')" :icon="mdiPlus" label="Add"
color="info" rounded-full small />
color="modern" rounded-full small />
</SectionTitleLineWithButton>
<!-- <label>{{ form.search }}</label> -->
<NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
@ -87,7 +88,7 @@ const destroy = async (id) => {
<input type="search" v-model="form.search"
class="rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
placeholder="Search" />
<BaseButton label="Search" type="submit" color="info"
<BaseButton label="Search" type="submit" color="modern"
class="ml-4 inline-flex items-center px-4 py-2" />
</div>
</div>

View File

@ -1,92 +1,67 @@
<script setup>
import { Head, Link, useForm } from "@inertiajs/vue3"
import {
mdiAccountKey,
mdiArrowLeftBoldOutline,
} from "@mdi/js"
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue"
import SectionMain from "@/Components/SectionMain.vue"
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue"
import CardBox from "@/Components/CardBox.vue"
import BaseButton from "@/Components/BaseButton.vue"
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { mdiAccountKey, mdiArrowLeftBoldOutline } from '@mdi/js';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import CardBox from '@/Components/CardBox.vue';
import BaseButton from '@/Components/BaseButton.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
const props = defineProps({
user: {
type: Object,
default: () => ({}),
},
roles: {
type: Object,
default: () => ({}),
},
userHasRoles: {
type: Object,
default: () => ({}),
}
})
defineProps({
user: {
type: Object,
default: () => ({}),
},
roles: {
type: Object,
default: () => ({}),
},
userHasRoles: {
type: Object,
default: () => ({}),
},
});
</script>
<template>
<LayoutAuthenticated :user="user">
<Head title="View user" />
<SectionMain>
<SectionTitleLineWithButton :icon="mdiAccountKey" title="View user" main>
<BaseButton :route-name="stardust.route('user.index')" :icon="mdiArrowLeftBoldOutline" label="Back" color="white"
rounded-full small />
</SectionTitleLineWithButton>
<CardBox class="mb-6">
<table>
<tbody>
<tr>
<td class="
p-4
pl-8
text-slate-500
dark:text-slate-400
hidden
lg:block
">
Login
</td>
<td data-label="Login">
{{ user.login }}
</td>
</tr>
<tr>
<td class="
p-4
pl-8
text-slate-500
dark:text-slate-400
hidden
lg:block
">
Email
</td>
<td data-label="Email">
{{ user.email }}
</td>
</tr>
<tr>
<td class="
p-4
pl-8
text-slate-500
dark:text-slate-400
hidden
lg:block
">
Created
</td>
<td data-label="Created">
{{ new Date(user.created_at).toLocaleString() }}
</td>
</tr>
</tbody>
</table>
</CardBox>
</SectionMain>
</LayoutAuthenticated>
<LayoutAuthenticated :user="user">
<Head title="View user" />
<SectionMain>
<SectionTitleLineWithButton :icon="mdiAccountKey" title="View user" main>
<BaseButton
:route-name="stardust.route('user.index')"
:icon="mdiArrowLeftBoldOutline"
label="Back"
color="white"
rounded-full
small
/>
</SectionTitleLineWithButton>
<CardBox class="mb-6">
<table>
<tbody>
<tr>
<td class="p-4 pl-8 text-slate-500 dark:text-slate-400 hidden lg:block">Login</td>
<td data-label="Login">
{{ user.login }}
</td>
</tr>
<tr>
<td class="p-4 pl-8 text-slate-500 dark:text-slate-400 hidden lg:block">Email</td>
<td data-label="Email">
{{ user.email }}
</td>
</tr>
<tr>
<td class="p-4 pl-8 text-slate-500 dark:text-slate-400 hidden lg:block">Created</td>
<td data-label="Created">
{{ new Date(user.created_at).toLocaleString() }}
</td>
</tr>
</tbody>
</table>
</CardBox>
</SectionMain>
</LayoutAuthenticated>
</template>

View File

@ -31,60 +31,70 @@ import FormControl from '@/Components/FormControl.vue';
</script> -->
<template>
<LayoutGuest>
<LayoutGuest>
<Head title="Login" />
<Head title="Login" />
<SectionFullScreen v-slot="{ cardClass }" :bg="'greenBlue'">
<CardBox :class="cardClass" form @submit.prevent="submit">
<FormValidationErrors v-bind:errors="errors" />
<SectionFullScreen v-slot="{ cardClass }" :bg="'greenBlue'">
<CardBox :class="cardClass" form @submit.prevent="submit">
<FormValidationErrors v-bind:errors="errors" />
<NotificationBarInCard v-if="status" color="info">
{{ status }}
</NotificationBarInCard>
<NotificationBarInCard v-if="status" color="info">
{{ status }}
</NotificationBarInCard>
<FormField label="Email" label-for="email" help="Please enter your email">
<FormControl v-model="form.email" :icon="mdiAccount" id="email" autocomplete="email" type="email" required />
</FormField>
<FormField label="Email" label-for="email" help="Please enter your email">
<FormControl v-model="form.email" :icon="mdiAccount" id="email" autocomplete="email" type="email"
required />
</FormField>
<FormField label="Password" label-for="password" help="Please enter your password">
<FormControl
v-model="form.password"
:icon="mdiAsterisk"
type="password"
id="password"
autocomplete="current-password"
required
/>
</FormField>
<FormField label="Password" label-for="password" help="Please enter your password">
<FormControl v-model="form.password" :icon="mdiAsterisk" type="password" id="password"
autocomplete="current-password" required />
</FormField>
<FormCheckRadioGroup v-model="form.remember" name="remember" :options="{ remember: 'Remember' }" />
<FormCheckRadioGroup v-model="form.remember" name="remember" :options="{ remember: 'Remember' }" />
<!-- <NotificationBar v-if="flash && flash.message" color="warning" :icon="mdiAlertBoxOutline">
<!-- <NotificationBar v-if="flash && flash.message" color="warning" :icon="mdiAlertBoxOutline">
{{ flash.message }}
class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4"
</NotificationBar> -->
<div v-if="flash && flash.message" class="flex flex-col mt-6 animate-fade-in">
<div class="bg-yellow-500 border-l-4 border-orange-400 text-white p-4" role="alert">
<p class="font-bold">Be Warned</p>
<p>{{ flash.message }}</p>
</div>
</div>
<div v-if="flash && flash.message" class="flex flex-col mt-6 animate-fade-in">
<div class="bg-yellow-500 border-l-4 border-orange-400 text-white p-4" role="alert">
<p class="font-bold">Be Warned</p>
<p>{{ flash.message }}</p>
</div>
</div>
<BaseDivider />
<BaseDivider />
<!-- buttons -->
<BaseLevel>
<BaseButtons>
<BaseButton type="submit" color="info" label="Login" :class="{ 'opacity-25': form.processing }"
v-bind:disabled="form.processing" />
<!-- <BaseButton v-if="canResetPassword" :route-name="route('password.request')" color="info" outline
<!-- buttons -->
<BaseLevel>
<BaseButtons>
<BaseButton
type="submit"
color="info"
label="Login"
:class="{ 'opacity-25': form.processing }"
v-bind:disabled="form.processing"
/>
<!-- <BaseButton v-if="canResetPassword" :route-name="route('password.request')" color="info" outline
label="Remind" /> -->
</BaseButtons>
<Link :href="stardust.route('app.register.show')"> Register </Link>
</BaseLevel>
</CardBox>
</SectionFullScreen>
</LayoutGuest>
</BaseButtons>
<!-- <Link :href="stardust.route('app.register.show')"> Register </Link> -->
</BaseLevel>
</CardBox>
</SectionFullScreen>
</LayoutGuest>
</template>
<script setup>
import { useForm, Head, Link } from '@inertiajs/vue3';
<script setup lang="ts">
import { useForm, Head } from '@inertiajs/vue3';
import { Ref } from 'vue';
// import { Head, Link, useForm } from '@inertiajs/inertia-vue3';
import { mdiAccount, mdiAsterisk } from '@mdi/js';
import LayoutGuest from '@/Layouts/LayoutGuest.vue';
@ -101,45 +111,45 @@ import NotificationBarInCard from '@/Components/NotificationBarInCard.vue';
import BaseLevel from '@/Components/BaseLevel.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
import NotificationBar from '@/Components/NotificationBar.vue';
// import NotificationBar from '@/Components/NotificationBar.vue';
import { computed } from 'vue';
import { usePage } from '@inertiajs/vue3';
// interface IErrorMessage {
// [key: string]: Array<string>;
// }
const flash = computed(() => {
return usePage().props.flash;
const flash: Ref<any> = computed(() => {
return usePage().props.flash;
});
const props = defineProps({
canResetPassword: Boolean,
status: {
type: String,
default: null,
},
errors: {
type: Object,
default: () => ({}),
},
defineProps({
canResetPassword: Boolean,
status: {
type: String,
default: null,
},
errors: {
type: Object,
default: () => ({}),
},
});
const form = useForm({
email: '',
password: '',
remember: [],
email: '',
password: '',
remember: [],
});
const submit = async() => {
await form
.transform((data) => ({
...data,
remember: form.remember && form.remember.length ? 'on' : '',
}))
.post(stardust.route('login.store'), {
// onFinish: () => {
// form.reset('password');
// }
});
const submit = async () => {
await form
.transform((data) => ({
...data,
remember: form.remember && form.remember.length ? 'on' : '',
}))
.post(stardust.route('login.store'), {
onFinish: () => {
form.reset('password');
},
});
};
</script>

View File

@ -1,10 +1,10 @@
<script setup>
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { computed, ref, onMounted } from 'vue';
import { MainService } from '@/Stores/main';
// import { Inertia } from '@inertiajs/inertia';
import {
mdiAccountMultiple,
mdiCartOutline,
mdiDatabaseOutline,
mdiChartTimelineVariant,
mdiFinance,
@ -13,7 +13,7 @@ import {
mdiGithub,
mdiChartPie,
} from '@mdi/js';
import { containerMaxW } from '@/config.js'; // "xl:max-w-6xl xl:mx-auto"
// import { containerMaxW } from '@/config.js'; // "xl:max-w-6xl xl:mx-auto"
import * as chartConfig from '@/Components/Charts/chart.config.js';
import LineChart from '@/Components/Charts/LineChart.vue';
import SectionMain from '@/Components/SectionMain.vue';
@ -27,7 +27,7 @@ import CardBoxClient from '@/Components/CardBoxClient.vue';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import SectionBannerStarOnGitHub from '@/Components/SectionBannerStarOnGitea.vue';
const chartData = ref(null);
const chartData = ref();
const fillChartData = () => {
chartData.value = chartConfig.sampleChartData();
};
@ -42,7 +42,7 @@ mainService.fetch('history');
mainService.fetchApi('authors');
mainService.fetchApi('datasets');
const clientBarItems = computed(() => mainService.clients.slice(0, 4));
// const clientBarItems = computed(() => mainService.clients.slice(0, 4));
const transactionBarItems = computed(() => mainService.history);
const authorBarItems = computed(() => mainService.authors.slice(0, 4));
@ -109,9 +109,9 @@ const datasets = computed(() => mainService.datasets);
<SectionTitleLineWithButton :icon="mdiAccountMultiple" title="Submitters (to do)" />
<!-- <NotificationBar color="info" :icon="mdiMonitorCellphone">
<NotificationBar color="info" :icon="mdiMonitorCellphone">
<b>Responsive table.</b> Collapses on mobile
</NotificationBar> -->
</NotificationBar>
<CardBox :icon="mdiMonitorCellphone" title="Responsive table" has-table>
<TableSampleClients />

View File

@ -0,0 +1,604 @@
<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 LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import CardBox from '@/Components/CardBox.vue';
import FormField from '@/Components/FormField.vue';
import FormControl from '@/Components/FormControl.vue';
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
// import BaseDivider from '@/Components/BaseDivider.vue';
import BaseButton from '@/Components/BaseButton.vue';
// import BaseButtons from '@/Components/BaseButtons.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
// import { Inertia } from '@inertiajs/inertia';
import CardBoxModal from '@/Components/CardBoxModal.vue';
import BaseIcon from '@/Components/BaseIcon.vue';
import IconWizard from '@/Components/Icons/Wizard.vue';
import IconMandatory from '@/Components/Icons/Mandatory.vue';
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';
const props = defineProps({
licenses: {
type: Object,
default: () => ({}),
},
doctypes: {
type: Object,
default: () => ({}),
},
titletypes: {
type: Object,
default: () => ({}),
},
descriptiontypes: {
type: Object,
default: () => ({}),
},
errors: {
type: Object,
default: () => ({}),
},
});
// const form = useForm({
// language: '',
// licenses: [],
// type: '',
// titles: [{ value: '', type: 'Main', language: Dataset.language }],
// });
// 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({
// language: language,
// licenses: [],
// rights: false,
// type: '',
// creating_corporation: 'Tethys RDR',
// titles: [{ value: '', type: 'Main', language: language }],
// descriptions: [{ value: '', type: 'Abstract', language: language }],
// });
const form = useForm<Dataset>(dataset);
// dataset.language.value = 'de';
// const emit = defineEmits(['update:modelValue', 'setRef']);
// computed({
// get: () => form.rights,
// set: (value) => {
// emit('update:modelValue', value);
// },
// });
const isModalActive = ref(false);
const formStep = ref(1);
// const submit = async () => {
// await router.post(stardust.route('user.store'), form, {
// onSuccess: () => {
// form.reset(), (formStep.value = 1);
// },
// });
// };
const nextStep = async () => {
let route = stardust.route('dataset.first.step');
if (formStep.value == 2) {
route = stardust.route('dataset.second.step');
}
// formStep.value++;
await form
.transform((data) => ({
...data,
rights: form.rights && form.rights == true ? 'true' : 'false',
}))
.post(route, {
form,
onSuccess: () => {
formStep.value++;
},
});
};
const prevStep = () => {
formStep.value--;
};
const addTitle = () => {
let newTitle: Title = { value: '', language: '', type: '' };
//this.dataset.files.push(uploadedFiles[i]);
form.titles.push(newTitle);
};
const removeTitle = (key) => {
form.titles.splice(key, 1);
};
const addDescription = () => {
let newDescription = { value: '', language: '', type: '' };
//this.dataset.files.push(uploadedFiles[i]);
form.descriptions.push(newDescription);
};
const removeDescription = (key) => {
form.descriptions.splice(key, 1);
};
</script>
<template>
<CardBoxModal v-model="isModalActive" title="Einverständniserklärung *">
Mit dem Setzen des Hakens bestätige ich hiermit
<ul class="list-decimal">
<li>
die Data Policy von Tethys RDR sowie die Terms & Conditions von Tethys gelesen und verstanden zu haben (<a
href="/docs/HandbuchTethys.pdf"
target="_blank"
>siehe hier</a
>)
</li>
<li>das Einverständnis aller Co-Autoren über die bevorstehende Datenpublikation schriftlich eingeholt zu haben</li>
<li>sowohl mit der Data Policy als auch mit den Terms & Conditions einverstanden zu sein</li>
</ul>
</CardBoxModal>
<LayoutAuthenticated>
<Head title="Submit Dataset" />
<SectionMain>
<SectionTitleLineWithButton :icon="mdiDatabasePlus" title="Submit dataset" main>
<!-- <BaseButton :route-name="stardust.route('user.index')" :icon="mdiArrowLeftBoldOutline" label="Back"
color="white" rounded-full small /> -->
</SectionTitleLineWithButton>
<CardBox>
<div class="mx-4 p-4">
<div class="flex items-center">
<!-- <label>{{ form.titles[0].language }}</label>
<label>{{ form.language }}</label> -->
<icon-wizard :is-current="formStep == 1" :is-checked="formStep > 1" :label="'Language'">
<icon-language></icon-language>
</icon-wizard>
<icon-wizard :is-current="formStep == 2" :is-checked="formStep > 2" :label="'Mandatory'">
<icon-mandatory></icon-mandatory>
</icon-wizard>
<icon-wizard :is-current="formStep == 3" :is-checked="formStep > 3" :label="'Recommendet'">
<icon-recommendet></icon-recommendet>
</icon-wizard>
<icon-wizard :is-current="formStep == 4" :is-checked="false" :label="'Confirm'" :is-last-step="true">
<icon-confirm></icon-confirm>
</icon-wizard>
</div>
</div>
<!-- mt-8: margin-top: 2rem; /* 32px */ 4 p-4: spacing 1rem 16px-->
<div class="mt-8 p-4">
<div v-if="formStep == 1">
<div class="flex flex-col md:flex-row">
<FormField
label="Language *"
help="required: select dataset main language"
:class="{ 'text-red-400': errors.language }"
class="w-full mx-2 flex-1"
>
<FormControl
required
v-model="form.language"
:type="'select'"
placeholder="[Enter Language]"
:errors="form.errors.language"
:options="['de', 'en']"
>
<div class="text-red-400 text-sm" v-if="form.errors.language">
{{ form.errors.language.join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<FormField
label="Licenses"
wrap-body
:class="{ 'text-red-400': form.errors.licenses }"
class="mt-8 w-full mx-2 flex-1"
>
<FormCheckRadioGroup v-model="form.licenses" name="roles" is-column :options="props.licenses" />
</FormField>
<!-- <label for="rights">
<input class="form-checkbox" name="rights" id="rights" type="checkbox" v-model="dataset.rights" />
terms and conditions
</label> -->
<FormField
label="Rights"
help="You must agree to continue"
wrap-body
:class="{ 'text-red-400': form.errors.rights }"
class="mt-8 w-full mx-2 flex-1 flex-col"
>
<label for="rights" class="checkbox mr-6 mb-3 last:mr-0">
<input type="checkbox" id="rights" required v-model="form.rights" />
<span class="check" />
<a class="pl-2" target="_blank">terms and conditions </a>
<!-- <BaseButton color="modern" :icon="mdiInformationOutline" small @click="isModalActive = true" /> -->
<BaseIcon
v-if="mdiInformationOutline"
:path="mdiInformationOutline"
@click.prevent="isModalActive = true"
/>
</label>
</FormField>
<div class="text-red-400 text-sm" v-if="errors.rights && Array.isArray(errors.rights)">
<!-- {{ errors.password_confirmation }} -->
{{ errors.rights.join(', ') }}
</div>
</div>
<div v-if="formStep == 2">
<!-- <CardBox title="Performance" :icon="mdiFinance" :header-icon="mdiReload" class="mb-6"> -->
<div class="flex flex-col md:flex-row">
<FormField
label="Dataset Type *"
help="required: dataset type"
:class="{ 'text-red-400': form.errors.type }"
class="w-full mx-2 flex-1"
>
<FormControl
required
v-model="form.type"
:type="'select'"
placeholder="-- select type --"
:errors="errors.type"
:options="doctypes"
>
<div class="text-red-400 text-sm" v-if="form.errors.type && Array.isArray(form.errors.type)">
{{ form.errors.type.join(', ') }}
</div>
</FormControl>
</FormField>
<!-- <div class="w-full mx-2 flex-1 svelte-1l8159u"></div> -->
<!-- Creating Corporation -->
<FormField
label="Creating Corporation *"
:class="{ 'text-red-400': form.errors.creating_corporation }"
class="w-full mx-2 flex-1"
>
<FormControl
required
v-model="form.creating_corporation"
type="text"
placeholder="[enter creating corporation]"
:is-read-only="true"
>
<div
class="text-red-400 text-sm"
v-if="form.errors.creating_corporation && Array.isArray(form.errors.creating_corporation)"
>
{{ form.errors.creating_corporation.join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<!-- <BaseDivider /> -->
<!-- titles -->
<CardBox
class="mb-6"
:has-form-data="true"
title="Titles"
:icon="mdiFinance"
:header-icon="mdiPlusCircle"
v-on:header-icon-click="addTitle()"
>
<!-- <div class="py-6 border-t border-gray-100 dark:border-slate-800"> -->
<div class="flex flex-col md:flex-row">
<FormField
label="Main Title *"
help="required: main title"
:class="{ 'text-red-400': form.errors['titles.0.value'] }"
class="w-full mx-2 flex-1"
>
<FormControl required v-model="form.titles[0].value" type="text" placeholder="[enter main title]">
<div
class="text-red-400 text-sm"
v-if="form.errors['titles.0.value'] && Array.isArray(form.errors['titles.0.value'])"
>
{{ form.errors['titles.0.value'].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField
label="Main Title Language*"
help="required: main title language"
:class="{ 'text-red-400': form.errors['titles.0.language'] }"
class="w-full mx-2 flex-1"
>
<FormControl required v-model="form.titles[0].language" type="text" :is-read-only="true">
<div
class="text-red-400 text-sm"
v-if="form.errors['titles.0.language'] && Array.isArray(form.errors['titles.0.language'])"
>
{{ form.errors['titles.0.language'].join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<label v-if="form.titles.length > 1">additional titles </label>
<!-- <BaseButton :icon="mdiPlusCircle" @click.prevent="addTitle()" color="modern" rounded-full small /> -->
<div v-if="form.titles.length > 1">
<div v-for="(item, index) in form.titles">
<div class="flex flex-col md:flex-row" v-if="item.type != 'Main'">
<FormField v-if="item.type != 'Main'" label="Remove">
<BaseButton
:icon="mdiMinusCircle"
class="mt-1"
@click.prevent="removeTitle(index)"
color="modern"
small
/>
</FormField>
<FormField
label="Title Value *"
:class="{ 'text-red-400': form.errors[`titles.${index}.value`] }"
class="w-full mx-2 flex-1"
>
<FormControl
required
v-model="form.titles[index].value"
type="text"
placeholder="[enter main title]"
>
<div class="text-red-400 text-sm" v-if="form.errors[`titles.${index}.value`]">
{{ form.errors[`titles.${index}.value`].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField
label="Title Type*"
:class="{ 'text-red-400': form.errors[`titles.${index}.type`] }"
class="w-full mx-2 flex-1"
>
<FormControl
required
v-model="form.titles[index].type"
type="select"
:options="titletypes"
placeholder="[select title type]"
>
<div class="text-red-400 text-sm" v-if="Array.isArray(form.errors[`titles.${index}.type`])">
{{ form.errors[`titles.${index}.type`].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField
label="Title Language*"
:class="{ 'text-red-400': form.errors[`titles.${index}.language`] }"
class="w-full mx-2 flex-1"
>
<FormControl
required
v-model="form.titles[index].language"
type="select"
:options="['de', 'en']"
placeholder="[select title language]"
>
<div class="text-red-400 text-sm" v-if="form.errors[`titles.${index}.language`]">
{{ form.errors[`titles.${index}.language`].join(', ') }}
</div>
</FormControl>
</FormField>
</div>
</div>
</div>
<!-- </div> -->
</CardBox>
<!-- Descriptions -->
<CardBox
class="mb-6"
:has-form-data="true"
title="Descriptions"
:header-icon="mdiPlusCircle"
v-on:header-icon-click="addDescription()"
>
<div class="flex flex-col md:flex-row">
<FormField
label="Main Abstract *"
help="required: main abstract"
:class="{ 'text-red-400': form.errors['descriptions.0.value'] }"
class="w-full mx-2 flex-1"
>
<FormControl
required
v-model="form.descriptions[0].value"
type="textarea"
placeholder="[enter main abstract]"
>
<div
class="text-red-400 text-sm"
v-if="form.errors['descriptions.0.value'] && Array.isArray(form.errors['descriptions.0.value'])"
>
{{ form.errors['descriptions.0.value'].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField
label="Main Title Language*"
help="required: main abstract language"
:class="{ 'text-red-400': form.errors['descriptions.0.language'] }"
class="w-full mx-2 flex-1"
>
<FormControl required v-model="form.descriptions[0].language" type="text" :is-read-only="true">
<div
class="text-red-400 text-sm"
v-if="
form.errors['descriptions.0.value'] && Array.isArray(form.errors['descriptions.0.language'])
"
>
{{ form.errors['descriptions.0.language'].join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<label v-if="form.descriptions.length > 1">additional descriptions: </label>
<!-- <BaseButton :icon="mdiPlusCircle" @click.prevent="addTitle()" color="modern" rounded-full small /> -->
<div v-if="form.descriptions.length > 1">
<div v-for="(item, index) in form.descriptions">
<div class="flex flex-col md:flex-row" v-if="item.type != 'Abstract'">
<FormField v-if="item.type != 'Abstract'" label="Remove">
<BaseButton
:icon="mdiMinusCircle"
class="mt-1"
@click.prevent="removeDescription(index)"
color="modern"
small
/>
</FormField>
<FormField
label="Description Value *"
:class="{ 'text-red-400': form.errors[`descriptions.${index}.value`] }"
class="w-full mx-2 flex-1"
>
<FormControl
required
v-model="form.descriptions[index].value"
type="text"
placeholder="[enter additional description]"
>
<div
class="text-red-400 text-sm"
v-if="
form.errors[`descriptions.${index}.value`] &&
Array.isArray(form.errors[`descriptions.${index}.value`])
"
>
{{ form.errors[`descriptions.${index}.value`].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField
label="Description Type *"
:class="{ 'text-red-400': form.errors[`descriptions.${index}.type`] }"
class="w-full mx-2 flex-1"
>
<FormControl
required
v-model="form.descriptions[index].type"
type="select"
:options="descriptiontypes"
placeholder="[select description type]"
>
<div
class="text-red-400 text-sm"
v-if="
form.errors[`descriptions.${index}.type`] &&
Array.isArray(form.errors[`descriptions.${index}.type`])
"
>
{{ form.errors[`descriptions.${index}.type`].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField
label="Description Language*"
:class="{ 'text-red-400': form.errors[`titdescriptionsles.${index}.language`] }"
class="w-full mx-2 flex-1"
>
<FormControl
required
v-model="form.descriptions[index].language"
type="select"
:options="['de', 'en']"
placeholder="[select title language]"
>
<div
class="text-red-400 text-sm"
v-if="form.errors && Array.isArray(form.errors[`descriptions.${index}.language`])"
>
{{ form.errors[`descriptions.${index}.language`].join(', ') }}
</div>
</FormControl>
</FormField>
</div>
</div>
</div>
</CardBox>
<CardBox class="mb-6" :has-form-data="true" title="Authors" :icon="mdiBookOpenPageVariant" >
<SearchAutocomplete
source="/api/persons"
:response-property="'first_name'"
placeholder="search in person table...."
></SearchAutocomplete>
</CardBox>
<!-- <SectionTitleLineWithButton :icon="mdiChartPie" title="Trends overview (to do publications per year)" class="flex flex-col md:flex-row" >
</SectionTitleLineWithButton> -->
</div>
<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>
<div v-if="formStep == 4">
<label>To Do: File Upload</label>
</div>
</div>
<template #footer>
<div class="flex p-2 mt-4">
<button
v-if="formStep > 1"
@click="prevStep"
class="text-base hover:scale-110 focus:outline-none flex justify-center px-4 py-2 rounded font-bold cursor-pointer hover:bg-gray-200 bg-gray-100 text-gray-700 border duration-200 ease-in-out border-gray-600 transition"
>
Previous
</button>
<div class="flex-auto flex flex-row-reverse">
<button
v-if="formStep < 4"
@click="nextStep"
class="text-base ml-2 hover:scale-110 focus:outline-none flex justify-center px-4 py-2 rounded font-bold cursor-pointer hover:bg-teal-600 bg-teal-600 text-teal-100 border duration-200 ease-in-out border-teal-600 transition"
>
Next
</button>
<!-- <button
class="text-base hover:scale-110 focus:outline-none flex justify-center px-4 py-2 rounded font-bold cursor-pointer hover:bg-teal-200 bg-teal-100 text-teal-700 border duration-200 ease-in-out border-teal-600 transition"
>
Skip
</button> -->
</div>
</div>
</template>
</CardBox>
</SectionMain>
</LayoutAuthenticated>
</template>

View File

@ -1,62 +0,0 @@
import { defineStore } from 'pinia';
import axios from 'axios';
export const MainService = defineStore('main', {
state: () => ({
/* User */
userName: '',
userEmail: null,
userAvatar: null,
/* Field focus with ctrl+k (to register only once) */
isFieldFocusRegistered: false,
/* Sample data for starting dashboard(commonly used) */
clients: [],
history: [],
authors: [],
datasets: []
}),
actions: {
// payload = authenticated user
setUser(payload) {
if (payload.name) {
this.userName = payload.name;
}
if (payload.email) {
this.userEmail = payload.email;
}
if (payload.avatar) {
this.userAvatar = payload.avatar;
}
},
fetch(sampleDataKey) {
// sampleDataKey= clients or history
axios
.get(`data-sources/${sampleDataKey}.json`)
.then((r) => {
if (r.data && r.data.data) {
this[sampleDataKey] = r.data.data;
}
})
.catch((error) => {
alert(error.message);
});
},
fetchApi(sampleDataKey) {
// sampleDataKey= clients or history
axios
.get(`api/${sampleDataKey}`)
.then((r) => {
if (r.data) {
this[sampleDataKey] = r.data;
}
})
.catch((error) => {
alert(error.message);
});
},
},
});

View File

@ -0,0 +1,79 @@
import { defineStore } from 'pinia';
import axios from 'axios';
interface Person {
id: number;
name: string;
email: string;
datasetCount: string;
created_at: string;
}
interface TransactionItem {
amount: number;
account: string;
name: string;
date: string;
type: string;
business: string;
}
export const MainService = defineStore('main', {
state: () => ({
/* User */
userName: '',
userEmail: null,
userAvatar: null,
/* Field focus with ctrl+k (to register only once) */
isFieldFocusRegistered: false,
/* Sample data for starting dashboard(commonly used) */
clients: [],
history: [] as Array<TransactionItem>,
authors: [] as Array<Person>,
datasets: [],
}),
actions: {
// payload = authenticated user
setUser(payload) {
if (payload.name) {
this.userName = payload.name;
}
if (payload.email) {
this.userEmail = payload.email;
}
if (payload.avatar) {
this.userAvatar = payload.avatar;
}
},
fetch(sampleDataKey) {
// sampleDataKey= clients or history
axios
.get(`data-sources/${sampleDataKey}.json`)
.then((r) => {
if (r.data && r.data.data) {
this[sampleDataKey] = r.data.data;
}
})
.catch((error) => {
alert(error.message);
});
},
fetchApi(sampleDataKey) {
// sampleDataKey= authors or datasets
axios
.get(`api/${sampleDataKey}`)
.then((r) => {
if (r.data) {
this[sampleDataKey] = r.data;
}
})
.catch((error) => {
alert(error.message);
});
},
},
});

View File

@ -3,11 +3,11 @@ import { createApp, h } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { createInertiaApp, Link, usePage } from '@inertiajs/vue3';
import DefaultLayout from '@/Layouts/Default.vue';
// import DefaultLayout from '@/Layouts/Default.vue';
import { createPinia } from 'pinia';
import { StyleService } from '@/Stores/style.js';
import { LayoutService } from '@/Stores/layout.js';
import { StyleService } from '@/Stores/style';
import { LayoutService } from '@/Stores/layout';
import { MainService } from '@/Stores/main';
import { darkModeKey, styleKey } from '@/config';
const pinia = createPinia();
@ -15,41 +15,36 @@ const pinia = createPinia();
import { initRoutes } from '@eidellev/adonis-stardust/client';
initRoutes();
// import { defineProps } from 'vue';
// const props = defineProps({
// user: {
// type: Object,
// default: () => ({}),
// },
// });
// import '@fontsource/archivo-black/index.css';
// import '@fontsource/inter/index.css';
createInertiaApp({
progress: {
color: '#4B5563',
},
// resolve: (name) => {
// const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
// return pages[`./Pages/${name}.vue`]
// },
// Webpack
// resolve: (name) => require(`./Pages/${name}`),
// resolve: (name) => require(`./Pages/${name}.vue`),
// add default layout
resolve: (name) => {
const page = require(`./Pages/${name}.vue`).default;
// if (!page.layout) {
// page.layout = DefaultLayout;
// }
return page;
},
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.use(pinia)
// .component('inertia-link', Link)
.mount(el);
},
progress: {
// color: '#4B5563',
color: '#22C55E',
},
// resolve: (name) => {
// const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
// return pages[`./Pages/${name}.vue`]
// },
// Webpack
// resolve: (name) => require(`./Pages/${name}`),
// resolve: (name) => require(`./Pages/${name}.vue`),
// add default layout
resolve: (name) => {
const page = require(`./Pages/${name}.vue`).default;
// if (!page.layout) {
// page.layout = DefaultLayout;
// }
return page;
},
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.use(pinia)
// .component('inertia-link', Link)
.mount(el);
},
});
const styleService = StyleService(pinia);
@ -62,14 +57,14 @@ styleService.setStyle(localStorage[styleKey] ?? 'basic');
/* Dark mode */
if (
(!localStorage[darkModeKey] && window.matchMedia('(prefers-color-scheme: dark)').matches) ||
localStorage[darkModeKey] === '1'
(!localStorage[darkModeKey] && window.matchMedia('(prefers-color-scheme: dark)').matches) ||
localStorage[darkModeKey] === '1'
) {
styleService.setDarkMode(true);
styleService.setDarkMode(true);
}
/* Collapse mobile aside menu on route change */
Inertia.on('navigate', (event) => {
layoutService.isAsideMobileExpanded = false;
layoutService.isAsideLgActive = false;
layoutService.isAsideMobileExpanded = false;
layoutService.isAsideLgActive = false;
});

View File

@ -22,6 +22,7 @@ export const colorsText = {
danger: 'text-red-500',
warning: 'text-yellow-500',
info: 'text-blue-500',
modern: 'text-teal-500',
};
export const colorsOutline = {
@ -44,6 +45,7 @@ export const getButtonColor = (color, isOutlined, hasHover) => {
danger: 'bg-red-600 dark:bg-red-500 text-white',
warning: 'bg-yellow-600 dark:bg-yellow-500 text-white',
info: 'bg-blue-600 dark:bg-blue-500 text-white',
modern: 'bg-teal-600 dark:bg-teal-500 text-white',
},
bgHover: {
white: 'hover:bg-gray-50',
@ -56,6 +58,7 @@ export const getButtonColor = (color, isOutlined, hasHover) => {
warning:
'hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600',
info: 'hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-blue-600 hover:dark:border-blue-600',
modern: 'hover:bg-emerald-700 hover:border-emerald-700 hover:dark:bg-emerald-600 hover:dark:border-emerald-600',
},
borders: {
white: 'border-gray-100',
@ -65,6 +68,7 @@ export const getButtonColor = (color, isOutlined, hasHover) => {
danger: 'border-red-600 dark:border-red-500',
warning: 'border-yellow-600 dark:border-yellow-500',
info: 'border-blue-600 dark:border-blue-500',
modern: 'border-teal-600 dark:border-teal-500',
},
text: {
white: 'text-black dark:text-slate-100',
@ -74,6 +78,7 @@ export const getButtonColor = (color, isOutlined, hasHover) => {
danger: 'text-red-600 dark:text-red-500',
warning: 'text-yellow-600 dark:text-yellow-500',
info: 'text-blue-600 dark:text-blue-500',
modern: 'text-teal-600 dark:text-teal-500',
},
outlineHover: {
white: 'hover:bg-gray-100 hover:text-gray-900 dark:hover:text-slate-900',
@ -87,6 +92,7 @@ export const getButtonColor = (color, isOutlined, hasHover) => {
warning:
'hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600',
info: 'hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-blue-600',
modern: 'hover:bg-teal-600 hover:text-teal hover:dark:bg-teal-100 hover:dark:text-black',
},
};

View File

@ -1,43 +0,0 @@
import {
mdiAccountCircle,
mdiMonitor,
mdiGithub,
mdiAccountKey,
mdiAccountEye,
mdiAccountGroup,
mdiPalette,
} from '@mdi/js'
export default [
{
route: 'dashboard',
icon: mdiMonitor,
label: 'Dashboard'
},
// {
// route: 'permission.index',
// icon: mdiAccountKey,
// label: 'Permissions'
// },
// {
// route: 'role.index',
// icon: mdiAccountEye,
// label: 'Roles'
// },
{
route: 'user.index',
icon: mdiAccountGroup,
label: 'Users'
},
{
route: 'role.index',
icon: mdiAccountEye,
label: 'Roles'
},
{
href: 'https://gitea.geologie.ac.at/geolba/tethys',
icon: mdiGithub,
label: 'Gitea',
target: '_blank'
}
]

46
resources/js/menu.ts Normal file
View File

@ -0,0 +1,46 @@
import {
mdiMonitor,
mdiGithub,
mdiAccountEye,
mdiAccountGroup,
mdiDatabasePlus,
} from '@mdi/js';
export default [
{
route: 'dashboard',
icon: mdiMonitor,
label: 'Dashboard',
},
// {
// route: 'permission.index',
// icon: mdiAccountKey,
// label: 'Permissions'
// },
// {
// route: 'role.index',
// icon: mdiAccountEye,
// label: 'Roles'
// },
{
route: 'user.index',
icon: mdiAccountGroup,
label: 'Users',
},
{
route: 'role.index',
icon: mdiAccountEye,
label: 'Roles',
},
{
route: 'dataset.create',
icon: mdiDatabasePlus,
label: 'Create Dataset',
},
{
href: 'https://gitea.geologie.ac.at/geolba/tethys',
icon: mdiGithub,
label: 'Gitea',
target: '_blank',
},
];

View File

@ -10,10 +10,10 @@
|
*/
import 'reflect-metadata'
import sourceMapSupport from 'source-map-support'
import { Ignitor } from '@adonisjs/core/build/standalone'
import 'reflect-metadata';
import sourceMapSupport from 'source-map-support';
import { Ignitor } from '@adonisjs/core/build/standalone';
sourceMapSupport.install({ handleUncaughtExceptions: false })
sourceMapSupport.install({ handleUncaughtExceptions: false });
new Ignitor(__dirname).httpServer().start()
new Ignitor(__dirname).httpServer().start();

View File

@ -123,3 +123,23 @@ Route.post('/edit-account-info/store/:id', 'UsersController.accountInfoStore')
.namespace('App/Controllers/Http/Admin')
.middleware(['auth']);
// Route::post('change-password', 'UserController@changePasswordStore')->name('admin.account.password.store');
Route.group(() => {
// Route.get('/user', 'UsersController.index').as('user.index');
Route.get('/dataset/create', 'DatasetController.create').as('dataset.create').middleware(['auth', 'can:dataset-submit']);
Route.post('/dataset/first/first-step', 'DatasetController.firstStep')
.as('dataset.first.step')
.middleware(['auth', 'can:dataset-submit']);
Route.post('/dataset/second/second-step', 'DatasetController.secondStep')
.as('dataset.second.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());
// Route.delete('/user/:id', 'UsersController.destroy').as('user.destroy').where('id', Route.matchers.number());
// Route.resource('user', 'DatasetController');
})
.namespace('App/Controllers/Http/Submitter')
.prefix('submitter');
// .middleware(['auth', 'can:dataset-list,dataset-publish']);
// .middleware(['auth', 'is:submitter']);

View File

@ -8,6 +8,7 @@ Route.group(() => {
Route.group(() => {
Route.get('authors', 'AuthorsController.index').as('author.index');
Route.get('datasets', 'DatasetController.index').as('dataset.index');
Route.get('persons', 'AuthorsController.persons').as('author.persons');
// Route.get("author/:id", "TodosController.show");
// Route.put("author/update", "TodosController.update");
// Route.post("author", "TodosController.store");

View File

@ -1,77 +1,86 @@
/** @type {import('tailwindcss').Config} */
const plugin = require("tailwindcss/plugin");
const plugin = require('tailwindcss/plugin');
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
content: ['./resources/**/*.{edge,js,ts,jsx,tsx,vue}'],
darkMode: 'class', // or 'media' or 'class'
theme: {
asideScrollbars: {
light: 'light',
gray: 'gray',
},
extend: {
zIndex: {
'-1': '-1',
},
flexGrow: {
5: '5',
},
maxHeight: {
'screen-menu': 'calc(100vh - 3.5rem)',
'modal': 'calc(100vh - 160px)',
},
transitionProperty: {
position: 'right, left, top, bottom, margin, padding',
textColor: 'color',
},
keyframes: {
'fade-out': {
from: { opacity: 1 },
to: { opacity: 0 },
},
'fade-in': {
from: { opacity: 0 },
to: { opacity: 1 },
},
},
animation: {
'fade-out': 'fade-out 250ms ease-in-out',
'fade-in': 'fade-in 250ms ease-in-out',
},
}, //extend
}, // theme
plugins: [
require("@tailwindcss/forms"),
plugin(function ({ matchUtilities, theme }) {
matchUtilities(
{
"aside-scrollbars": (value) => {
const track = value === "light" ? "100" : "900";
const thumb = value === "light" ? "300" : "600";
const color = value === "light" ? "gray" : value;
content: ['./resources/**/*.{edge,js,ts,jsx,tsx,vue}'],
darkMode: 'class', // or 'media' or 'class'
theme: {
asideScrollbars: {
light: 'light',
gray: 'gray',
},
extend: {
colors: {
'primary': '#22C55E',
'primary-dark': '#DCFCE7',
},
fontFamily: {
sans: ['Inter', ...defaultTheme.fontFamily.sans],
logo: ['Archivo Black', ...defaultTheme.fontFamily.sans],
},
zIndex: {
'-1': '-1',
},
flexGrow: {
5: '5',
},
maxHeight: {
'screen-menu': 'calc(100vh - 3.5rem)',
'modal': 'calc(100vh - 160px)',
},
transitionProperty: {
position: 'right, left, top, bottom, margin, padding',
textColor: 'color',
},
keyframes: {
'fade-out': {
from: { opacity: 1 },
to: { opacity: 0 },
},
'fade-in': {
from: { opacity: 0 },
to: { opacity: 1 },
},
},
animation: {
'fade-out': 'fade-out 250ms ease-in-out',
'fade-in': 'fade-in 250ms ease-in-out',
},
}, //extend
}, // theme
plugins: [
require('@tailwindcss/forms'),
plugin(function ({ matchUtilities, theme }) {
matchUtilities(
{
'aside-scrollbars': (value) => {
const track = value === 'light' ? '100' : '900';
const thumb = value === 'light' ? '300' : '600';
const color = value === 'light' ? 'gray' : value;
return {
scrollbarWidth: "thin",
scrollbarColor: `${theme(`colors.${color}.${thumb}`)} ${theme(
`colors.${color}.${track}`
)}`,
"&::-webkit-scrollbar": {
width: "8px",
height: "8px",
},
"&::-webkit-scrollbar-track": {
backgroundColor: theme(`colors.${color}.${track}`),
},
"&::-webkit-scrollbar-thumb": {
borderRadius: "0.25rem",
backgroundColor: theme(`colors.${color}.${thumb}`),
},
};
},
},
{ values: theme("asideScrollbars") }
);
}),
require("@tailwindcss/line-clamp"),
],
return {
'scrollbarWidth': 'thin',
'scrollbarColor': `${theme(`colors.${color}.${thumb}`)} ${theme(
`colors.${color}.${track}`,
)}`,
'&::-webkit-scrollbar': {
width: '8px',
height: '8px',
},
'&::-webkit-scrollbar-track': {
backgroundColor: theme(`colors.${color}.${track}`),
},
'&::-webkit-scrollbar-thumb': {
borderRadius: '0.25rem',
backgroundColor: theme(`colors.${color}.${thumb}`),
},
};
},
},
{ values: theme('asideScrollbars') },
);
}),
require('@tailwindcss/line-clamp'),
],
};

View File

@ -1,9 +1,12 @@
{
"extends": "adonis-preset-ts/tsconfig.json",
"include": [
"**/*"
// "**/*",
"**/*",
// "./resources/js/**/*"
// "./resources/js/**/*.vue",
// "./resources/js/**/*.ts",
// "./resources/js/**/*.vue",
],
"exclude": [
"node_modules",