From 7e42cd97eace5c5abf8f9a2099d0d694e1defd5d Mon Sep 17 00:00:00 2001 From: Alejandro Matos <42916953+amatosg@users.noreply.github.com> Date: Wed, 16 Oct 2019 01:41:11 -0500 Subject: [PATCH 01/16] fixing issues with translations there was several inconsistencies using formal and informal second person (fixed to use only formal) --- client/src/data/languages/es.js | 64 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/client/src/data/languages/es.js b/client/src/data/languages/es.js index 35ad7f3d..63de1c56 100644 --- a/client/src/data/languages/es.js +++ b/client/src/data/languages/es.js @@ -9,7 +9,7 @@ export default { 'REPEAT_PASSWORD': 'Repetir Contraseña', 'LOG_IN': 'Iniciar sesión', 'SIGN_UP': 'Registrarse', - 'FORGOT_PASSWORD': 'Olvidaste tu contraseña?', + 'FORGOT_PASSWORD': '¿Olvidó su contraseña?', 'RECOVER_PASSWORD': 'Recuperar Contraseña', 'RECOVER_SENT': 'Se ha enviado un email con instrucciones de recuperación.', 'NEW_EMAIL': 'Nuevo email', @@ -34,7 +34,7 @@ export default { 'NO_ATTACHMENT': 'No hay archivo adjunto', 'STAFF': 'Staff', 'CUSTOMER': 'Cliente', - 'YES': 'Si', + 'YES': 'Sí', 'NO': 'No', 'CANCEL': 'Cancelar', 'MY_ACCOUNT': 'Mi Cuenta', @@ -79,7 +79,7 @@ export default { 'ASSIGN_TO_ME': 'Asignar', 'UN_ASSIGN': 'Desasignar', 'VIEW_TICKET': 'Ver Ticket', - 'VIEW_TICKET_DESCRIPTION': 'Revisa el estado de tu ticket usando el número de ticket y tu email.', + 'VIEW_TICKET_DESCRIPTION': 'Revise el estado de su ticket usando el número de ticket y su email.', 'SELECT_CUSTOM_RESPONSE': 'Seleccionar una respuesta personalizada...', 'WARNING': 'Advertencia', 'INFO': 'Información', @@ -127,7 +127,7 @@ export default { 'UPDATE_PASSWORD': 'Actualizar contraseña', 'UPDATE_LEVEL': 'Actualizar nivel', 'UPDATE_DEPARTMENTS': 'Actializar departamentos', - 'EDIT_STAFF': 'Edit staff member', + 'EDIT_STAFF': 'Editar miembro del staff', 'ADD_DEPARTMENT': 'Añadir departamento', 'UPDATE_DEPARTMENT': 'Actualizar Departamento', 'TRANSFER_TICKETS_TO': 'Transferir tickets a', @@ -161,7 +161,7 @@ export default { 'ALLOWED_LANGUAGES': 'Idiomas Permitidos', 'ALLOWED_LANGUAGES_INFO': 'Los idiomas permitidos son los idiomas que puede utilizar un usuario.', 'SETTINGS_UPDATED': 'Se han actualizado las configuraciones', - 'ON': 'Si', + 'ON': 'Sí', 'OFF': 'No', 'BOXED': 'Boxed', 'FULL_WIDTH': 'Ancho Total', @@ -175,8 +175,8 @@ export default { 'INCLUDE_USERS_VIA_CSV': 'Incluir usuarios por archivo CSV', 'BACKUP_DATABASE': 'Backup database', 'DELETE_ALL_USERS': 'Borrar todos los usuarios', - 'PLEASE_CONFIRM_PASSWORD': 'Por favor confirma tu contraseña para hacer estos cambios', - 'REGISTRATION_API_KEYS': 'Registration API keys', + 'PLEASE_CONFIRM_PASSWORD': 'Por favor, confirme su contraseña para hacer estos cambios', + 'REGISTRATION_API_KEYS': 'Registro de API keys', 'NAME_OF_KEY': 'Nombre de la clave', 'KEY': 'Clave', 'ADD_API_KEY': 'Añadir nueva clave', @@ -209,7 +209,7 @@ export default { 'OPTION': 'Opción {índice}', 'OPTIONS': 'Opciones', 'FIELD_DESCRIPTION': 'Descripción del campo (opcional)', - 'DESCRIPTION_ADD_CUSTOM_TAG': 'Aquí puedes agregar una nueva etiqueta personalizada', + 'DESCRIPTION_ADD_CUSTOM_TAG': 'Aquí puede agregar una nueva etiqueta personalizada', 'DESCRIPTION_EDIT_CUSTOM_TAG': 'aquí se puede editar una etiqueta personalizada', 'CUSTOM_FIELDS': 'Campos Personalizados', @@ -228,11 +228,11 @@ export default { 'ACTIVITY_RE_OPEN': 'reabrió el ticket', 'ACTIVITY_DEPARTMENT_CHANGED': 'cambió el departamento del ticket', 'ACTIVITY_PRIORITY_CHANGED': 'cambió la prioridad del ticket', - 'ACTIVITY_EDIT_COMMENT': 'editado un comentario del billete', + 'ACTIVITY_EDIT_COMMENT': 'editó un comentario del ticket', 'ACTIVITY_EDIT_SETTINGS': 'editó las preferencias', 'ACTIVITY_SIGNUP': 'se registró', - 'ACTIVITY_ADD_TOPIC': 'añadio el tema', + 'ACTIVITY_ADD_TOPIC': 'añadió el tema', 'ACTIVITY_ADD_ARTICLE': 'añadió el articulo', 'ACTIVITY_DELETE_TOPIC': 'eliminó el tema', 'ACTIVITY_DELETE_ARTICLE': 'eliminó el artículo', @@ -268,13 +268,13 @@ export default { 'ADMIN_NAME': 'Nombre de la cuenta admin', 'ADMIN_EMAIL': 'Email de la cuenta admin', 'ADMIN_PASSWORD': 'Contraseña de la cuenta admin', - 'ADMIN_PASSWORD_DESCRIPTION': 'Por favor recuerde esta contraseña. Es necesaria para acceder al panel de administración. Puedes cambiarla después.', + 'ADMIN_PASSWORD_DESCRIPTION': 'Por favor, recuerde esta contraseña. Es necesaria para acceder al panel de administración. Puedes cambiarla después.', 'INSTALLATION_COMPLETED': 'Instalación Completada.', - 'INSTALLATION_COMPLETED_DESCRIPTION': 'La instalación de OpenSupports esta completada. Redirigiendo al panel de administración...', + 'INSTALLATION_COMPLETED_DESCRIPTION': 'La instalación de OpenSupports ha finalizado. Redirigiendo al panel de administración...', 'STEP_TITLE': 'Paso {current} de {total} - {title}', 'STEP_1_DESCRIPTION': 'Seleccione su idioma preferido para el asistente de instalación.', - 'STEP_2_DESCRIPTION': 'Aqui estan listados los requisitos para OpenSupports. Por favor, asegúrese de cumplir con todos los requisitos.', + 'STEP_2_DESCRIPTION': 'Aqui están listados los requisitos para OpenSupports. Por favor, asegúrese de cumplir con todos los requisitos.', 'STEP_3_DESCRIPTION': 'Por favor, complete la configuracion de la base de datos MySQL.', 'STEP_4_DESCRIPTION': 'Por favor, seleccione sus preferencias de sistema de usuarios.', 'STEP_5_DESCRIPTION': 'Pro favor, seleccione sus preferencias generales del sistema.', @@ -284,28 +284,28 @@ export default { //VIEW DESCRIPTIONS 'CREATE_TICKET_DESCRIPTION': 'Este es un formulario para crear tickets. Rellene el formulario y envíenos sus dudas. Nuestro sistema de soporte responderá lo antes posible.', 'TICKET_LIST_DESCRIPTION': 'Aquí puede encontrar una lista de todos los tickets que ha enviado a nuestro equipo de soporte.', - 'TICKETS_DESCRIPTION': 'Envíe un ticket a través de nuestro centro de soporte y obtenga respuesta de sus dudas, sugerencias y problemas.', - 'ARTICLES_DESCRIPTION': 'Echa un vistazo a nuestros artículos sobre temas comunes, guías y documentación.', + 'TICKETS_DESCRIPTION': 'Envíe un ticket a través de nuestro centro de soporte y obtenga respuesta de sus dudas, sugerencias o problemas.', + 'ARTICLES_DESCRIPTION': 'Eche un vistazo a nuestros artículos sobre temas comunes, guías y documentación.', 'ACCOUNT_DESCRIPTION': 'Todos sus tickets están almacenados en el perfil de su cuenta. Mantenga un registro de todos los tickets envíados a nuestro equipo de soporte.', 'SUPPORT_CENTER_DESCRIPTION': 'Bienvenido a nuestro centro de soporte. Puede ponerse en contacto con nosotros a través de un sistema de tickets. Sus tickets serán contestados por nuestro personal.', 'CUSTOM_RESPONSES_DESCRIPTION': 'Las respuestas personalizadas son respuestas automatizadas para problemas comunes.', - 'CUSTOM_TAGS_DESCRIPTION': 'Aquí puede ver administrar las etiquetas personalizadas de los tickets para identificarlos mejor', - 'MY_TICKETS_DESCRIPTION': 'Aquí puedes ver los tickets que tienes asignado.', - 'NEW_TICKETS_DESCRIPTION': 'Aquí puedes ver todos los tickets nuevos que no están asignados por nadie.', - 'ALL_TICKETS_DESCRIPTION': 'Aquí puede ver los tickets de los departamentos a cuales estas asignado.', - 'TICKET_VIEW_DESCRIPTION': 'Este ticket ha sido enviado por un cliente. Aquí puede responderlo o asignarselo.', - 'BAN_USERS_DESCRIPTION': 'Aquí puedes ver la lista de email bloqueados. Puedes des-bloquear o agregar más emails a la lista.', - 'LIST_USERS_DESCRIPTION': 'Esta es la lista de usuarios que estan registrados en el sistema. Puedes buscar a alguien en particular, bloquearlo o borralo.', + 'CUSTOM_TAGS_DESCRIPTION': 'Aquí puede ver y administrar las etiquetas personalizadas de los tickets para identificarlos mejor', + 'MY_TICKETS_DESCRIPTION': 'Aquí puede ver los tickets que tienes asignado.', + 'NEW_TICKETS_DESCRIPTION': 'Aquí puede ver todos los tickets nuevos que no están asignados a nadie.', + 'ALL_TICKETS_DESCRIPTION': 'Aquí puede ver los tickets de los departamentos a cuales está asignado.', + 'TICKET_VIEW_DESCRIPTION': 'Este ticket ha sido enviado por un cliente. Aquí puede responderlo o asignárselo.', + 'BAN_USERS_DESCRIPTION': 'Aquí puede ver la lista de email bloqueados. Puede desbloquear o agregar más emails a la lista.', + 'LIST_USERS_DESCRIPTION': 'Esta es la lista de usuarios que están registrados en el sistema. Puede buscar a alguien en particular, bloquearlo o borralo.', 'USER_VIEW_DESCRIPTION': 'Aquí puede encontrar toda la información sobre un usuario y todos los tickets enviados por el usuario. También puede eliminarlo o bloquearlo.', - 'DELETE_USER_DESCRIPTION': 'El usuario no podrá iniciar sesión y todas sus tickets se borrarán. Además, el correo electrónico ya no puede utilizarse.', + 'DELETE_USER_DESCRIPTION': 'El usuario no podrá iniciar sesión y todos sus tickets se borrarán. Además, el correo electrónico ya no puede utilizarse.', 'DELETE_TOPIC_DESCRIPTION': 'Al borrar el tema, todos los artículos en él se borrarán.', 'EDIT_TOPIC_DESCRIPTION': 'Aquí puede cambiar el nombre, el icono y el color del icono del tema.', 'ADD_ARTICLE_DESCRIPTION': 'Aquí puede agregar un artículo que estará disponible para cada usuario. Se agregará dentro de la categoría {category}.', 'LIST_ARTICLES_DESCRIPTION': 'Esta es una lista de artículos que incluye información sobre nuestros servicios.', 'ADD_TOPIC_DESCRIPTION': 'Aquí puede agregar un tema que funciona como una categoría para los artículos.', - 'DELETE_ARTICLE_DESCRIPTION': 'Vas a borrar este artículo para siempre.', - 'STAFF_MEMBERS_DESCRIPTION': 'Aquí puedes ver quienes son los miembros de staff.', - 'ADD_STAFF_DESCRIPTION': 'Usa este formulario para agregar miembros de staff.', + 'DELETE_ARTICLE_DESCRIPTION': 'Va a borrar este artículo para siempre.', + 'STAFF_MEMBERS_DESCRIPTION': 'Aquí puede ver quiénes son los miembros de staff.', + 'ADD_STAFF_DESCRIPTION': 'Use este formulario para agregar miembros de staff.', 'EDIT_STAFF_DESCRIPTION': 'Aquí puede editar información sobre un miembro de staff.', 'MY_ACCOUNT_DESCRIPTION': 'Aquí puede editar información acerca de usted.', 'DEPARTMENTS_DESCRIPTION': 'Un departamento es un grupo donde los tickets pueden ir. Se utilizan para categorizar los tickets. Puede asignarlos a otros miembros de staff.', @@ -322,12 +322,12 @@ export default { 'REGISTRATION_ENABLED': 'Se ha habilitado el registro', 'ADD_API_KEY_DESCRIPTION': 'Inserte en nombre y una Registration API Key será generada.', 'SIGN_UP_VIEW_DESCRIPTION': 'Aquí puede crear una cuenta para nuestro centro de soporte. Es necesario para enviar tickets y ver la documentación.', - 'EDIT_PROFILE_VIEW_DESCRIPTION': 'Aquí puedes editar tu usuario cambiando tu correo electrónico o tu contraseña.', - 'ENABLE_USER_SYSTEM_DESCRIPTION': 'Habilitar/Deshabilitar el uso de un sistema de usuario. Si lo deshabilitas, todos los usuarios serán eliminados pero los tickets serán guardados. Si lo habilitas, se crearán los usuarios de los tickets existentes.', + 'EDIT_PROFILE_VIEW_DESCRIPTION': 'Aquí puede editar su usuario cambiando el correo electrónico o la contraseña.', + 'ENABLE_USER_SYSTEM_DESCRIPTION': 'Habilitar/Deshabilitar el uso de un sistema de usuario. Si lo deshabilita, todos los usuarios serán eliminados pero los tickets serán guardados. Si lo habilita, se crearán los usuarios de los tickets existentes.', 'CSV_DESCRIPTION': 'El archivo CSV debe tener 3 columnas: correo electrónico, contraseña, nombre. No hay límite en el recuento de filas. Se creará un usuario por fila en el archivo.', 'SMTP_SERVER_DESCRIPTION': 'La configuracion de SMTP permite que la applicacion mande emails. Si no es configurado, ningún mail sera enviado OpenSupports.', 'IMAP_SERVER_DESCRIPTION': 'La configuración del servidor IMAP permite que la aplicación cree tickets de los correos electrónicos enviados a un buzón.', - 'ENABLE_USER_DESCRIPTION': 'Esta acción permite al usuario iniciar sesión y crear tickets..', + 'ENABLE_USER_DESCRIPTION': 'Esta acción permite al usuario iniciar sesión y crear tickets.', 'DISABLE_USER_DESCRIPTION': 'El usuario estará deshabilitado y no podrá iniciar sesión y crear tickets.', 'PRIVATE_RESPONSE_DESCRIPTION': 'Esta respuesta solo será vista por los miembros del personal.', 'PRIVATE_TOPIC_DESCRIPTION': 'Este tema solo será visto por los miembros del personal.', @@ -375,7 +375,7 @@ export default { 'TICKET_SENT': 'El ticket se ha creado correctamente.', 'VALID_RECOVER': 'La contraseña se recuperó correctamente', 'EMAIL_EXISTS': 'El email ya existe', - 'ARE_YOU_SURE': '¿Estás seguro?', + 'ARE_YOU_SURE': '¿Está seguro?', 'EMAIL_WILL_CHANGE': 'El correo electrónico actual se cambiará', 'PASSWORD_WILL_CHANGE': 'Se cambiará la contraseña actual', 'EMAIL_CHANGED': 'Se ha cambiado correctamente el correo electrónico', @@ -397,7 +397,7 @@ export default { 'SUCCESSFUL_CONNECTION': 'Conexión exitosa', 'UNSUCCESSFUL_CONNECTION': 'Conexión fallida', 'SERVER_CREDENTIALS_WORKING': 'Las credenciales del servidor están funcionando correctamente', - 'DELETE_CUSTOM_FIELD_SURE': 'Algunos usuarios pueden estar usando este campo. ¿Seguro que quieres borrarlo?', + 'DELETE_CUSTOM_FIELD_SURE': 'Algunos usuarios pueden estar usando este campo. ¿Seguro que quiere borrarlo?', 'COMMENT_EDITED': '(Comentario editado)', 'LAST_7_DAYS': 'Últimos 7 dias', @@ -414,7 +414,7 @@ export default { 'ACTIVITY_RE_OPEN_THIS': 'reabrió este ticket', 'ACTIVITY_DEPARTMENT_CHANGED_THIS': 'cambió el departamento de este ticket a ', 'ACTIVITY_PRIORITY_CHANGED_THIS': 'cambió la prioridad de este ticket a ', - 'DATE_PREFIX': 'el dia', + 'DATE_PREFIX': 'el día', 'LEFT_EMPTY_DATABASE': 'Dejar vacío para la creación automática de bases de datos', 'REMEMBER_ME': 'Recordarme', 'EMAIL_LOWERCASE': 'email', From 3435a8f2df4b6bcaebe859c2fd6dd0cf789eb2c4 Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Mon, 6 Jan 2020 12:38:32 -0300 Subject: [PATCH 02/16] Create autocomplete component first part --- .../core-components/autocomplete-dropdown.js | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 client/src/core-components/autocomplete-dropdown.js diff --git a/client/src/core-components/autocomplete-dropdown.js b/client/src/core-components/autocomplete-dropdown.js new file mode 100644 index 00000000..8e9b1b4f --- /dev/null +++ b/client/src/core-components/autocomplete-dropdown.js @@ -0,0 +1,66 @@ +import React from 'react'; +import _ from 'lodash'; + +import DropDown from 'core-components/drop-down'; +import Menu from 'core-components/menu'; +import Tag from 'core-components/tag'; + +class AutocompleteDropDown extends React.Component { + + static propTypes = { + items: Menu.propTypes.items, + }; + + state = { + selectedIndex: 0, + itemsSelected: [], + }; + + render() { + return ( +
+ +
+ ); + } + + getDropdownList() { + const {items} = this.props; + return this.getUnselectedList(items, this.state.itemsSelected); + } + + getUnselectedList(list, selectedList) { + return list.filter(item => !_.some(selectedList, item)); + } + + renderSelectedItems() { + console.log('itemsSelected: ', this.state.itemsSelected); + return this.state.itemsSelected.map(item => this.renderSelectedItem(item)); + } + + renderSelectedItem(item) { + console.log(item.id); + return + } + + onRemoveClick(itemId) { + this.setState({ + itemsSelected: this.state.itemsSelected.filter(item => item.id != itemId), + }); + } + +} + +export default AutocompleteDropDown; From 76cfbc87535f07627ee3405e3d792310ade5467e Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Mon, 6 Jan 2020 12:48:25 -0300 Subject: [PATCH 03/16] Minors changes of line break in tag select and dropdown --- client/src/core-components/drop-down.js | 3 ++- client/src/core-components/tag-selector.js | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/core-components/drop-down.js b/client/src/core-components/drop-down.js index 5dc03f80..abcbee4e 100644 --- a/client/src/core-components/drop-down.js +++ b/client/src/core-components/drop-down.js @@ -77,7 +77,6 @@ class DropDown extends React.Component { let item = this.props.items[this.getSelectedIndex()]; let iconNode = null; - if (item.icon) { iconNode = ; } @@ -234,10 +233,12 @@ class DropDown extends React.Component { } getSelectedIndex() { + return (this.props.selectedIndex !== undefined) ? this.props.selectedIndex : this.state.selectedIndex; } modulo(number, mod) { + return ((number % mod) + mod) % mod; } } diff --git a/client/src/core-components/tag-selector.js b/client/src/core-components/tag-selector.js index 3de95502..83d58e93 100644 --- a/client/src/core-components/tag-selector.js +++ b/client/src/core-components/tag-selector.js @@ -27,19 +27,18 @@ class TagSelector extends React.Component { renderSelectedTags() { const itemList = this.props.values.map(value => _.find(this.props.items, {name:value})); - + return itemList.map(this.renderSelectedTag.bind(this)); } - renderSelectedTag(item,index) { + return ; - } renderTagOptions() { const itemList = _.filter(this.props.items,(item) => !_.includes(this.props.values,item.name)); - + return itemList.map(this.renderTagOption.bind(this)); } From b52a65497e3144c376a81a00c9378cd7dd5357ec Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Mon, 6 Jan 2020 12:52:13 -0300 Subject: [PATCH 04/16] Demopage does work --- client/src/app/demo/components-demo-page.js | 277 ++------------------ 1 file changed, 16 insertions(+), 261 deletions(-) diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 5555ce23..eab74996 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -1,268 +1,23 @@ -'use strict'; +import AutocompleteDropDown from 'core-components/autocomplete-dropdown'; const React = require('react'); -// const LineChart = require("react-chartjs-2").Line; const _ = require('lodash'); -const DocumentTitle = require('react-document-title'); -const ModalContainer = require('app-components/modal-container'); -const AreYouSure = require('app-components/are-you-sure'); - -const Button = require('core-components/button'); -const Input = require('core-components/input'); -const Checkbox = require('core-components/checkbox'); -const Widget = require('core-components/widget'); -const DropDown = require('core-components/drop-down'); -const Menu = require('core-components/menu'); -const Tooltip = require('core-components/tooltip'); -const Table = require('core-components/table'); -const InfoTooltip = require('core-components/info-tooltip'); -const TagSelector = require('core-components/tag-selector'); - -function rand(min, max, num) { - var rtn = []; - while (rtn.length < num) { - rtn.push((Math.random() * (max - min)) + min); - } - return rtn; +class DemoPage extends React.Component { + + render() { + let itemsList = [ + {id: 45, content: 'content item 1'}, + {id: 46, content: 'content item 2'}, + {id: 47, content: 'content item 3'}, + {id: 48, content: 'content item 4'}, + {id: 49, content: 'content item 5'}, + ]; + + return ( + + ); + } } -let chartData = { - labels: ["January", "February", "March", "April", "May", "June"], - datasets: [ - { - label: "My Second dataset", - fill: false, - pointColor: "rgba(151,187,205,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(151,187,205,1)", - borderWidth: 3, - data: rand(32, 100, 6), - pointRadius: 0 - }, - { - label: "My Second dataset", - fill: false, - pointColor: "rgba(151,187,205,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(151,187,205,1)", - borderWidth: 3, - data: rand(32, 100, 6) - } - ] -}; -let chartOptions = {}; -let dropDownItems = [{content: 'English'}, {content: 'Spanish'}, {content: 'German'}, {content: 'Portuguese'}, {content: 'Japanese'}]; -let secondaryMenuItems = [ - {content: 'My Tickets', icon: 'file-text'}, - {content: 'New Ticket', icon: 'plus'}, - {content: 'Articles', icon: 'book'}, - {content: 'Edit Profile', icon: 'pencil'}, - {content: 'Close Session', icon: 'lock'} -]; - -let DemoPage = React.createClass({ - - propTypes: { - currentUser: React.PropTypes.object.isRequired - }, - - elements: [ - { - title: 'Primary Button', - render: ( - - ) - }, - { - title: 'Tag selector', - render: ( - console.log('deleted click', e)} - onTagSelected={(e) => console.log('selected click', e)} - /> - ) - }, - { - title: 'Input', - render: ( - - ) - }, - { - title: 'Input wrapped in a label', - render: ( - - ) - }, - { - title: 'Checkbox', - render: ( - - ) - }, - { - title: 'Widget', - render: ( - -

Register here!

- - -
- ) - }, - { - title: 'Primary Menu', - render: ( - - ) - }, - { - title: 'Secondary Menu', - render: ( - - ) - }, - { - title: 'Navigation Menu', - render: ( - - ) - }, - { - title: 'Horizontal Menu', - render: ( - - ) - }, - { - title: 'HorizontalList Menu', - render: ( - - ) - }, - { - title: 'DropDown', - render: ( - - ) - }, - { - title: 'Tooltip', - render: ( -
- - hola - -
- ) - }, - { - title: 'ModalTrigger', - render: ( - - ) - }, - { - title: 'ModalTrigger Large', - render: ( - - ) - }, - { - title: 'Table', - render: ( - b.title1) - ans = 1; - return ans; - }}/> - ) - }, - { - title: 'InfoTooltip', - render: ( - - ) - }, - { - title: 'LineChart', - // render: ( - // - // ), - render: ( - null - ) - } - ], - - render() { - return ( - -
- {this.renderElements()} -
-
- ); - }, - - renderElements: function () { - return this.elements.map((element) => { - return ( -
-

- {element.title} -

-
- {element.render} -
-
- ); - }); - } -}); - export default DemoPage; From 5705115f7359ec360ef189abd70d97c2f751fc26 Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Tue, 7 Jan 2020 22:32:26 -0300 Subject: [PATCH 05/16] Added search autocomplete input text. --- client/src/app/demo/components-demo-page.js | 19 ++- .../core-components/autocomplete-dropdown.js | 110 ++++++++++++---- .../autocomplete-dropdown.scss | 21 ++++ client/src/core-components/drop-down.js | 118 ++++++++++++++---- client/src/core-components/tag-selector.js | 6 +- 5 files changed, 223 insertions(+), 51 deletions(-) create mode 100644 client/src/core-components/autocomplete-dropdown.scss diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index eab74996..43df6744 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -4,18 +4,25 @@ const React = require('react'); const _ = require('lodash'); class DemoPage extends React.Component { + + state = { + selectedList: [] + } render() { let itemsList = [ - {id: 45, content: 'content item 1'}, - {id: 46, content: 'content item 2'}, - {id: 47, content: 'content item 3'}, - {id: 48, content: 'content item 4'}, - {id: 49, content: 'content item 5'}, + {id: 45, name: 'Lautaro', content: 'Lautaro.', color: 'red'}, + {id: 46, name: 'dsafa', content: 'dsafa', color: 'red'}, + {id: 47, name: 'asdasdasd', content: 'asdasdasd.', color: 'red'}, + {id: 48, name: '123123123', content: '123123123.', color: 'blue'}, + {id: 49, name: 'hola', content: 'hola', color: 'green'}, ]; return ( - + this.setState({selectedList})}/> ); } } diff --git a/client/src/core-components/autocomplete-dropdown.js b/client/src/core-components/autocomplete-dropdown.js index 8e9b1b4f..f434011c 100644 --- a/client/src/core-components/autocomplete-dropdown.js +++ b/client/src/core-components/autocomplete-dropdown.js @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { createRef }from 'react'; import _ from 'lodash'; +import keyCode from 'keycode'; import DropDown from 'core-components/drop-down'; import Menu from 'core-components/menu'; @@ -12,55 +13,122 @@ class AutocompleteDropDown extends React.Component { }; state = { - selectedIndex: 0, itemsSelected: [], + inputValue: "", + opened: false, + highlightedIndex: 0, }; render() { + let inputWith = 0; + if(this.span) { + this.span.style.display = 'inline'; + this.span.textContent = this.state.inputValue; + inputWith = Math.ceil(this.span.getBoundingClientRect().width) + 20 + this.span.style.display = 'none'; + } + return (
-
); } + renderSelectedItems() { + return this.state.itemsSelected.map(item => this.renderSelectedItem(item)); + } + + renderSelectedItem(item) { + return + } + getDropdownList() { const {items} = this.props; - return this.getUnselectedList(items, this.state.itemsSelected); + const list = this.getUnselectedList(items, this.state.itemsSelected); + + return list.filter(s => _.includes(s.name, this.state.inputValue)); } getUnselectedList(list, selectedList) { return list.filter(item => !_.some(selectedList, item)); } - renderSelectedItems() { - console.log('itemsSelected: ', this.state.itemsSelected); - return this.state.itemsSelected.map(item => this.renderSelectedItem(item)); - } + onRemoveClick(itemId, event) { + event.preventDefault(); - renderSelectedItem(item) { - console.log(item.id); - return - } - - onRemoveClick(itemId) { this.setState({ itemsSelected: this.state.itemsSelected.filter(item => item.id != itemId), + opened: false, + highlightedIndex: 0, + }); + } + + onChangeDropDown(e){ + if (this.getDropdownList().length) { + this.setState({ + itemsSelected: [...this.state.itemsSelected, this.getDropdownList()[e.index]], + inputValue: "", + highlightedIndex: 0, + }); + } + } + + onChangeInput(str){ + this.setState({ + inputValue: str, + opened: true, + highlightedIndex: 0, + }); + } + + onMenuToggle(b){ + this.setState({ + opened: b, }); } + onHighlightedIndexChange(n){ + this.setState({ + highlightedIndex: n, + }); + } + + onKeyDown(event){ + if (keyCode(event) === "space"){ + event.stopPropagation(); + } + + if (keyCode(event) === "backspace" && this.state.inputValue === ""){ + this.setState({ + itemsSelected: this.state.itemsSelected.slice(0,this.state.itemsSelected.length-1), + }); + + } + } } export default AutocompleteDropDown; diff --git a/client/src/core-components/autocomplete-dropdown.scss b/client/src/core-components/autocomplete-dropdown.scss new file mode 100644 index 00000000..f7940b11 --- /dev/null +++ b/client/src/core-components/autocomplete-dropdown.scss @@ -0,0 +1,21 @@ +@import "../scss/vars"; + +.autocomplete { + &__input { + display: inline-block; + border: 0; + background: transparent; + outline: none; + padding-left: 5px; + width: auto; + max-width: 100%; + min-width: 20px; + } + + .sizer { + line-height: 1.21428571em; + padding: .67857143em 2.1em .67857143em 1em; + display: none; + white-space: pre; + } +} diff --git a/client/src/core-components/drop-down.js b/client/src/core-components/drop-down.js index abcbee4e..e7247758 100644 --- a/client/src/core-components/drop-down.js +++ b/client/src/core-components/drop-down.js @@ -2,7 +2,7 @@ import React from 'react'; import _ from 'lodash'; import classNames from 'classnames'; import {Motion, spring} from 'react-motion'; -import keyCode from 'keycode'; +import keyCode from 'keycode'; import Menu from 'core-components/menu'; import Icon from 'core-components/icon'; @@ -14,7 +14,12 @@ class DropDown extends React.Component { selectedIndex: React.PropTypes.number, items: Menu.propTypes.items, onChange: React.PropTypes.func, - size: React.PropTypes.oneOf(['small', 'medium', 'large']) + size: React.PropTypes.oneOf(['small', 'medium', 'large']), + + highlightedIndex: React.PropTypes.number, + onHighlightedIndexChange: React.PropTypes.func, + opened: React.PropTypes.bool, + onMenuToggle: React.PropTypes.func, }; static defaultProps = { @@ -28,7 +33,7 @@ class DropDown extends React.Component { menuId: _.uniqueId('drop-down-menu_'), selectedIndex: props.selectedIndex || props.defaultSelectedIndex, highlightedIndex: props.selectedIndex || props.defaultSelectedIndex, - opened: false + opened: false, }; } @@ -44,7 +49,7 @@ class DropDown extends React.Component { return { defaultStyle: {opacity: 0, translateY: 20}, - style: (this.state.opened) ? openedStyle : closedStyle + style: (this.getOpen()) ? openedStyle : closedStyle }; } @@ -89,12 +94,17 @@ class DropDown extends React.Component { } getClass() { + const { + className, + size, + } = this.props; + let classes = { 'drop-down': true, - 'drop-down_closed': !this.state.opened, + 'drop-down_closed': !this.getOpen(), - ['drop-down_' + this.props.size]: (this.props.size), - [this.props.className]: (this.props.className) + ['drop-down_' + size]: (size), + [className]: (className) }; return classNames(classes); @@ -102,10 +112,10 @@ class DropDown extends React.Component { getCurrentItemProps() { return { - 'aria-expanded': this.state.opened, + 'aria-expanded': this.getOpen(), 'aria-autocomplete': 'list', 'aria-owns': this.state.menuId, - 'aria-activedescendant': this.state.menuId + '__' + this.state.highlightedIndex, + 'aria-activedescendant': this.state.menuId + '__' + this.getHighlightedIndex(), className: 'drop-down__current-item', onClick: this.handleClick.bind(this), onKeyDown: this.onKeyDown.bind(this), @@ -122,7 +132,7 @@ class DropDown extends React.Component { items: this.props.items, onItemClick: this.handleItemClick.bind(this), onMouseDown: this.handleListMouseDown.bind(this), - selectedIndex: this.state.highlightedIndex, + selectedIndex: this.getHighlightedIndex(), role: 'listbox' }; } @@ -137,8 +147,13 @@ class DropDown extends React.Component { } getKeyActions(event) { - const {highlightedIndex, opened} = this.state; + const highlightedIndex = this.getHighlightedIndex(); + const opened = this.getOpen(); const itemsQuantity = this.props.items.length; + const { + onHighlightedIndexChange, + onMenuToggle, + } = this.props; return { 'up': () => { @@ -148,6 +163,10 @@ class DropDown extends React.Component { this.setState({ highlightedIndex: this.modulo(highlightedIndex - 1, itemsQuantity) }); + + if (onHighlightedIndexChange){ + onHighlightedIndexChange(this.modulo(highlightedIndex - 1, itemsQuantity)); + } } }, 'down': () => { @@ -157,6 +176,10 @@ class DropDown extends React.Component { this.setState({ highlightedIndex: this.modulo(highlightedIndex + 1, itemsQuantity) }); + + if (onHighlightedIndexChange){ + onHighlightedIndexChange(this.modulo(highlightedIndex + 1, itemsQuantity)); + } } }, 'enter': () => { @@ -166,6 +189,10 @@ class DropDown extends React.Component { this.setState({ opened: true }); + + if (onMenuToggle) { + onMenuToggle(true); + } } }, 'space': () => { @@ -174,32 +201,54 @@ class DropDown extends React.Component { this.setState({ opened: true }); + + if (onMenuToggle) { + onMenuToggle(true); + } }, 'esc': () => { this.setState({ opened: false }); + + if (onMenuToggle) { + onMenuToggle(false); + } }, 'tab': () => { - if (this.state.opened) { + if (this.getOpen()) { event.preventDefault(); - this.onIndexSelected(highlightedIndex) + if (onHighlightedIndexChange){ + this.onIndexSelected(highlightedIndex) + } } } }; } handleBlur() { + const {onMenuToggle} = this.props; + this.setState({ opened: false }); + + if (onMenuToggle) { + onMenuToggle(false); + } } handleClick() { + const {onMenuToggle} = this.props; + this.setState({ - opened: !this.state.opened + opened: !this.getOpen() }); + + if (onMenuToggle) { + onMenuToggle(!this.getOpen()); + } } handleItemClick(index) { @@ -207,14 +256,28 @@ class DropDown extends React.Component { } onIndexSelected(index) { + const { + onMenuToggle, + onHighlightedIndexChange, + onChange, + } = this.props; + this.setState({ opened: false, selectedIndex: index, highlightedIndex: index }); + + if (onHighlightedIndexChange){ + onHighlightedIndexChange(index); + } - if (this.props.onChange) { - this.props.onChange({ + if (onMenuToggle) { + onMenuToggle(false); + } + + if (onChange) { + onChange({ index }); } @@ -225,22 +288,35 @@ class DropDown extends React.Component { } onAnimationFinished() { - if (!this.state.opened && this.state.highlightedIndex !== this.getSelectedIndex()) { + const {onHighlightedIndexChange} = this.props; + + + if (!this.getOpen() && this.getHighlightedIndex() !== this.getSelectedIndex()) { this.setState({ - highlightedIndex: this.getSelectedIndex() + highlightedIndex: this.getSelectedIndex(), }); + + if (onHighlightedIndexChange){ + onHighlightedIndexChange(this.getSelectedIndex()); + } } } getSelectedIndex() { - return (this.props.selectedIndex !== undefined) ? this.props.selectedIndex : this.state.selectedIndex; } - modulo(number, mod) { - return ((number % mod) + mod) % mod; } + + getOpen(){ + return (this.props.opened !== undefined) ? this.props.opened : this.state.opened; + } + + getHighlightedIndex() { + return (this.props.highlightedIndex !== undefined) ? this.props.highlightedIndex : this.state.highlightedIndex; + } + } export default DropDown; diff --git a/client/src/core-components/tag-selector.js b/client/src/core-components/tag-selector.js index 83d58e93..9576d5cf 100644 --- a/client/src/core-components/tag-selector.js +++ b/client/src/core-components/tag-selector.js @@ -38,13 +38,13 @@ class TagSelector extends React.Component { renderTagOptions() { const itemList = _.filter(this.props.items,(item) => !_.includes(this.props.values,item.name)); - + return itemList.map(this.renderTagOption.bind(this)); } - renderTagOption(item,index) { + renderTagOption(item) { return ( -
+
{item.name}
From 6ff46163b326037931b4650070ad8805e992f3e5 Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Wed, 8 Jan 2020 12:27:11 -0300 Subject: [PATCH 06/16] Added itemsSelectedList default --- client/src/app/demo/components-demo-page.js | 17 +++-- .../core-components/autocomplete-dropdown.js | 35 +++++++-- .../autocomplete-dropdown.scss | 3 +- client/src/core-components/drop-down.js | 73 ++++++++----------- client/src/core-components/tag-selector.js | 1 + 5 files changed, 74 insertions(+), 55 deletions(-) diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 43df6744..f522ef73 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -6,23 +6,30 @@ const _ = require('lodash'); class DemoPage extends React.Component { state = { - selectedList: [] + selectedList: [ + {id: 47, name: 'asdasdasd', content: 'asdasdasd.', color: 'red'}, + {id: 48, name: '123123123', content: '123123123.', color: 'blue'}, + {id: 49, name: 'hola', content: 'hola', color: 'green'}, + ] } render() { let itemsList = [ - {id: 45, name: 'Lautaro', content: 'Lautaro.', color: 'red'}, - {id: 46, name: 'dsafa', content: 'dsafa', color: 'red'}, + {id: 45, name: 'Lautaro', content: 'Lautaro.', color: 'gray'}, + {id: 46, name: 'dsafa', content: 'dsafa', color: 'black'}, {id: 47, name: 'asdasdasd', content: 'asdasdasd.', color: 'red'}, {id: 48, name: '123123123', content: '123123123.', color: 'blue'}, {id: 49, name: 'hola', content: 'hola', color: 'green'}, ]; return ( - + this.setState({selectedList})}/> + onChange={selectedList => this.setState({selectedList: selectedList})}/> + +
); } } diff --git a/client/src/core-components/autocomplete-dropdown.js b/client/src/core-components/autocomplete-dropdown.js index f434011c..3a0a320e 100644 --- a/client/src/core-components/autocomplete-dropdown.js +++ b/client/src/core-components/autocomplete-dropdown.js @@ -10,6 +10,9 @@ class AutocompleteDropDown extends React.Component { static propTypes = { items: Menu.propTypes.items, + onChange: React.PropTypes.func, + value: React.PropTypes.arrayOf(React.PropTypes.shape({ + })) }; state = { @@ -59,7 +62,7 @@ class AutocompleteDropDown extends React.Component { } renderSelectedItems() { - return this.state.itemsSelected.map(item => this.renderSelectedItem(item)); + return this.getValue().map(item => this.renderSelectedItem(item)); } renderSelectedItem(item) { @@ -67,8 +70,8 @@ class AutocompleteDropDown extends React.Component { } getDropdownList() { - const {items} = this.props; - const list = this.getUnselectedList(items, this.state.itemsSelected); + const { items, } = this.props; + const list = this.getUnselectedList(items, this.getValue()); return list.filter(s => _.includes(s.name, this.state.inputValue)); } @@ -78,23 +81,34 @@ class AutocompleteDropDown extends React.Component { } onRemoveClick(itemId, event) { + const { onChange, } = this.props; + event.preventDefault(); this.setState({ - itemsSelected: this.state.itemsSelected.filter(item => item.id != itemId), + itemsSelected: this.getValue().filter(item => item.id != itemId), opened: false, highlightedIndex: 0, }); + + onChange && onChange(this.getValue().filter(item => item.id != itemId)); + } onChangeDropDown(e){ + const { onChange, } = this.props; + if (this.getDropdownList().length) { this.setState({ - itemsSelected: [...this.state.itemsSelected, this.getDropdownList()[e.index]], + itemsSelected: [...this.getValue(), this.getDropdownList()[e.index]], inputValue: "", highlightedIndex: 0, }); + + onChange && onChange([...this.getValue(), this.getDropdownList()[e.index]]); + } + } onChangeInput(str){ @@ -118,17 +132,26 @@ class AutocompleteDropDown extends React.Component { } onKeyDown(event){ + const { onChange, } = this.props; + if (keyCode(event) === "space"){ event.stopPropagation(); } if (keyCode(event) === "backspace" && this.state.inputValue === ""){ this.setState({ - itemsSelected: this.state.itemsSelected.slice(0,this.state.itemsSelected.length-1), + itemsSelected: this.getValue().slice(0,this.getValue().length-1), + highlightedIndex: 0, }); + onChange && onChange(this.getValue().slice(0,this.getValue().length-1)); } } + + getValue() { + return (this.props.value !== undefined) ? this.props.value : this.state.itemsSelected; + } + } export default AutocompleteDropDown; diff --git a/client/src/core-components/autocomplete-dropdown.scss b/client/src/core-components/autocomplete-dropdown.scss index f7940b11..4e43c395 100644 --- a/client/src/core-components/autocomplete-dropdown.scss +++ b/client/src/core-components/autocomplete-dropdown.scss @@ -7,7 +7,8 @@ background: transparent; outline: none; padding-left: 5px; - width: auto; +// width: auto; + width: 20px; max-width: 100%; min-width: 20px; } diff --git a/client/src/core-components/drop-down.js b/client/src/core-components/drop-down.js index e7247758..7ff010d7 100644 --- a/client/src/core-components/drop-down.js +++ b/client/src/core-components/drop-down.js @@ -69,7 +69,10 @@ class DropDown extends React.Component { } renderList({opacity, translateY}) { - let style = { opacity: opacity, transform: `translateY(${translateY}px)`}; + let style = { + opacity: opacity, + transform: `translateY(${translateY}px)` + }; return (
@@ -164,9 +167,8 @@ class DropDown extends React.Component { highlightedIndex: this.modulo(highlightedIndex - 1, itemsQuantity) }); - if (onHighlightedIndexChange){ - onHighlightedIndexChange(this.modulo(highlightedIndex - 1, itemsQuantity)); - } + onHighlightedIndexChange && onHighlightedIndexChange(this.modulo(highlightedIndex - 1, itemsQuantity)); + } }, 'down': () => { @@ -177,9 +179,8 @@ class DropDown extends React.Component { highlightedIndex: this.modulo(highlightedIndex + 1, itemsQuantity) }); - if (onHighlightedIndexChange){ - onHighlightedIndexChange(this.modulo(highlightedIndex + 1, itemsQuantity)); - } + onHighlightedIndexChange && onHighlightedIndexChange(this.modulo(highlightedIndex + 1, itemsQuantity)); + } }, 'enter': () => { @@ -190,9 +191,8 @@ class DropDown extends React.Component { opened: true }); - if (onMenuToggle) { - onMenuToggle(true); - } + onMenuToggle && onMenuToggle(true); + } }, 'space': () => { @@ -202,53 +202,48 @@ class DropDown extends React.Component { opened: true }); - if (onMenuToggle) { - onMenuToggle(true); - } + onMenuToggle && onMenuToggle(true); + }, 'esc': () => { this.setState({ opened: false }); - if (onMenuToggle) { - onMenuToggle(false); - } + onMenuToggle && onMenuToggle(false); + }, 'tab': () => { if (this.getOpen()) { event.preventDefault(); - if (onHighlightedIndexChange){ - this.onIndexSelected(highlightedIndex) - } + onHighlightedIndexChange && this.onIndexSelected(highlightedIndex); + } } }; } handleBlur() { - const {onMenuToggle} = this.props; + const { onMenuToggle, } = this.props; this.setState({ opened: false }); - if (onMenuToggle) { - onMenuToggle(false); - } + onMenuToggle && onMenuToggle(false); + } handleClick() { - const {onMenuToggle} = this.props; + const { onMenuToggle, } = this.props; this.setState({ opened: !this.getOpen() }); - if (onMenuToggle) { - onMenuToggle(!this.getOpen()); - } + onMenuToggle && onMenuToggle(!this.getOpen()); + } handleItemClick(index) { @@ -268,19 +263,12 @@ class DropDown extends React.Component { highlightedIndex: index }); - if (onHighlightedIndexChange){ - onHighlightedIndexChange(index); - } + onHighlightedIndexChange && onHighlightedIndexChange(index); + + onMenuToggle && onMenuToggle(false); - if (onMenuToggle) { - onMenuToggle(false); - } - - if (onChange) { - onChange({ - index - }); - } + onChange && onChange({ index }); + } handleListMouseDown(event) { @@ -288,7 +276,7 @@ class DropDown extends React.Component { } onAnimationFinished() { - const {onHighlightedIndexChange} = this.props; + const { onHighlightedIndexChange, } = this.props; if (!this.getOpen() && this.getHighlightedIndex() !== this.getSelectedIndex()) { @@ -296,9 +284,8 @@ class DropDown extends React.Component { highlightedIndex: this.getSelectedIndex(), }); - if (onHighlightedIndexChange){ - onHighlightedIndexChange(this.getSelectedIndex()); - } + onHighlightedIndexChange && onHighlightedIndexChange(this.getSelectedIndex()); + } } diff --git a/client/src/core-components/tag-selector.js b/client/src/core-components/tag-selector.js index 9576d5cf..e432333e 100644 --- a/client/src/core-components/tag-selector.js +++ b/client/src/core-components/tag-selector.js @@ -13,6 +13,7 @@ class TagSelector extends React.Component { })), values: React.PropTypes.arrayOf(React.PropTypes.string), onRemoveClick: React.PropTypes.func, + onTagSelected: React.PropTypes.func, }; render() { From 8248ec4d037cf7671cb4656e3b5814240cb88542 Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Wed, 8 Jan 2020 18:11:26 -0300 Subject: [PATCH 07/16] Added autocomplete tagselector component --- client/src/app-components/ticket-viewer.js | 14 +++++- client/src/app/demo/components-demo-page.js | 2 +- .../core-components/autocomplete-dropdown.js | 34 ++++++++++--- client/src/core-components/tag-selector.js | 50 ++++++++----------- 4 files changed, 60 insertions(+), 40 deletions(-) diff --git a/client/src/app-components/ticket-viewer.js b/client/src/app-components/ticket-viewer.js index e965fe8e..720cd31a 100644 --- a/client/src/app-components/ticket-viewer.js +++ b/client/src/app-components/ticket-viewer.js @@ -134,7 +134,13 @@ class TicketViewer extends React.Component { onChange={this.onDepartmentDropdownChanged.bind(this)} />
{ticket.author.name}
-
+
+ +
{i18n('PRIORITY')}
@@ -143,7 +149,11 @@ class TicketViewer extends React.Component {
- +
{this.renderAssignStaffList()} diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index f522ef73..2ed988e4 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -26,7 +26,7 @@ class DemoPage extends React.Component {
this.setState({selectedList: selectedList})}/>
diff --git a/client/src/core-components/autocomplete-dropdown.js b/client/src/core-components/autocomplete-dropdown.js index 3a0a320e..36c2351c 100644 --- a/client/src/core-components/autocomplete-dropdown.js +++ b/client/src/core-components/autocomplete-dropdown.js @@ -11,8 +11,9 @@ class AutocompleteDropDown extends React.Component { static propTypes = { items: Menu.propTypes.items, onChange: React.PropTypes.func, - value: React.PropTypes.arrayOf(React.PropTypes.shape({ - })) + values: React.PropTypes.arrayOf(React.PropTypes.shape({})), + onRemoveClick: React.PropTypes.func, + onTagSelected: React.PropTypes.func, }; state = { @@ -30,7 +31,7 @@ class AutocompleteDropDown extends React.Component { inputWith = Math.ceil(this.span.getBoundingClientRect().width) + 20 this.span.style.display = 'none'; } - + return (
@@ -455,23 +457,47 @@ class TicketViewer extends React.Component { } addTag(tag) { + this.setState({ + tagSelectorLoading: true, + }) API.call({ path: '/ticket/add-tag', data: { ticketNumber: this.props.ticket.ticketNumber, tagId: tag } - }).then(this.onTicketModification.bind(this)) + }) + .then(() => { + this.setState({ + tagSelectorLoading: false, + }); + this.onTicketModification(); + }) + .catch(() => this.setState({ + tagSelectorLoading: false, + })) } removeTag(tag) { + this.setState({ + tagSelectorLoading: true, + }); + API.call({ path: '/ticket/remove-tag', data: { ticketNumber: this.props.ticket.ticketNumber, tagId: tag } - }).then(this.onTicketModification.bind(this)) + }).then(() => { + this.setState({ + tagSelectorLoading: false, + }); + + this.onTicketModification(); + }).catch(() => this.setState({ + tagSelectorLoading: false, + })) } onCustomResponsesChanged({index}) { diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 2ed988e4..4d07685c 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -3,6 +3,21 @@ import AutocompleteDropDown from 'core-components/autocomplete-dropdown'; const React = require('react'); const _ = require('lodash'); +const searchApi = (query) => { + const data = [ + {id: 11, name: 'ivan', content: 'Ivan.', color: 'red',}, + {id: 12, name: 'lautaro', content: 'Lautaro.', color: 'indigo',}, + {id: 13, name: 'javier', content: 'Javier.', color: 'cyan',}, + {id: 14, name: 'guillermo', content: 'Guillermo.', color: 'violet',}, + ] + + return new Promise((res,rej) => { + setTimeout(function () { + res(data.filter(item => _.includes(item.name, query))); + }, 500); + }) +}; + class DemoPage extends React.Component { state = { @@ -10,7 +25,8 @@ class DemoPage extends React.Component { {id: 47, name: 'asdasdasd', content: 'asdasdasd.', color: 'red'}, {id: 48, name: '123123123', content: '123123123.', color: 'blue'}, {id: 49, name: 'hola', content: 'hola', color: 'green'}, - ] + ], + selectedList2: [] } render() { @@ -21,7 +37,7 @@ class DemoPage extends React.Component { {id: 48, name: '123123123', content: '123123123.', color: 'blue'}, {id: 49, name: 'hola', content: 'hola', color: 'green'}, ]; - + return (
this.setState({selectedList: selectedList})}/> + + this.setState({selectedList2: selectedList})}/> + +
); } diff --git a/client/src/core-components/autocomplete-dropdown.js b/client/src/core-components/autocomplete-dropdown.js index 36c2351c..261c5d54 100644 --- a/client/src/core-components/autocomplete-dropdown.js +++ b/client/src/core-components/autocomplete-dropdown.js @@ -9,11 +9,13 @@ import Tag from 'core-components/tag'; class AutocompleteDropDown extends React.Component { static propTypes = { - items: Menu.propTypes.items, + //items: Menu.propTypes.items, onChange: React.PropTypes.func, values: React.PropTypes.arrayOf(React.PropTypes.shape({})), onRemoveClick: React.PropTypes.func, onTagSelected: React.PropTypes.func, + //getItemListFromQuery: React.propTypes.func, + disabled: React.PropTypes.bool, }; state = { @@ -21,10 +23,28 @@ class AutocompleteDropDown extends React.Component { inputValue: "", opened: false, highlightedIndex: 0, + items2: [], }; + componentDidMount() { + const { getItemListFromQuery, } = this.props; + + if (this.state.items2.length === 0){ + if(getItemListFromQuery !== undefined) { + getItemListFromQuery("") + .then(res => { + this.setState({ + items2: res, + }); + }); + } + + } + } + render() { let inputWith = 0; + if(this.span) { this.span.style.display = 'inline'; this.span.textContent = this.state.inputValue; @@ -34,7 +54,7 @@ class AutocompleteDropDown extends React.Component { return (
-
); } @@ -39,19 +40,5 @@ class TagSelector extends React.Component { ); } - onRemoveClick(tagId) { - const { onRemoveClick, } = this.props; - - onRemoveClick && onRemoveClick(tagId); - - } - - onTagSelected(tagId) { - const { onTagSelected, } = this.props; - - onTagSelected && onTagSelected(tagId); - - } - } export default TagSelector; From b267a29d06cd19b9192d74190438af852123eff7 Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Mon, 13 Jan 2020 22:00:51 -0300 Subject: [PATCH 09/16] Add loading, empty list message. (autocompplete component) --- client/src/app/demo/components-demo-page.js | 39 ++++-- .../core-components/autocomplete-dropdown.js | 121 ++++++++++++------ client/src/core-components/drop-down.js | 8 +- client/src/core-components/drop-down.scss | 19 +++ 4 files changed, 132 insertions(+), 55 deletions(-) diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 4d07685c..72ac03d0 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -3,18 +3,31 @@ import AutocompleteDropDown from 'core-components/autocomplete-dropdown'; const React = require('react'); const _ = require('lodash'); -const searchApi = (query) => { - const data = [ - {id: 11, name: 'ivan', content: 'Ivan.', color: 'red',}, - {id: 12, name: 'lautaro', content: 'Lautaro.', color: 'indigo',}, - {id: 13, name: 'javier', content: 'Javier.', color: 'cyan',}, - {id: 14, name: 'guillermo', content: 'Guillermo.', color: 'violet',}, - ] +const color = [ + 'red', + 'cyan', + 'blue', + 'green', +]; + +let countries = ["Afghanistan","Åland Islands","Albania","Algeria","American Samoa","AndorrA","Angola","Anguilla","Antarctica","Antigua and Barbuda","Argentina","Armenia","Aruba","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia and Herzegovina","Botswana","Bouvet Island","Brazil","British Indian Ocean Territory","Brunei Darussalam","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Cape Verde","Cayman Islands","Central African Republic","Chad","Chile","China","Christmas Island","Cocos (Keeling) Islands","Colombia","Comoros","Congo","Congo, The Democratic Republic of the","Cook Islands","Costa Rica","Cote D'Ivoire","Croatia","Cuba","Cyprus","Czech Republic","Denmark","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Falkland Islands (Malvinas)","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Heard Island and Mcdonald Islands","Holy See (Vatican City State)","Honduras","Hong Kong","Hungary","Iceland","India","Indonesia","Iran, Islamic Republic Of","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Korea, Democratic People'S Republic of","Korea, Republic of","Kuwait","Kyrgyzstan","Lao People'S Democratic Republic","Latvia","Lebanon","Lesotho","Liberia","Libyan Arab Jamahiriya","Liechtenstein","Lithuania","Luxembourg","Macao","Macedonia, The Former Yugoslav Republic of","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia, Federated States of","Moldova, Republic of","Monaco","Mongolia","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","Netherlands Antilles","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territory, Occupied","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn","Poland","Portugal","Puerto Rico","Qatar","Reunion","Romania","Russian Federation","RWANDA","Saint Helena","Saint Kitts and Nevis","Saint Lucia","Saint Pierre and Miquelon","Saint Vincent and the Grenadines","Samoa","San Marino","Sao Tome and Principe","Saudi Arabia","Senegal","Serbia and Montenegro","Seychelles","Sierra Leone","Singapore","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia and the South Sandwich Islands","Spain","Sri Lanka","Sudan","Suriname","Svalbard and Jan Mayen","Swaziland","Sweden","Switzerland","Syrian Arab Republic","Taiwan, Province of China","Tajikistan","Tanzania, United Republic of","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad and Tobago","Tunisia","Turkey","Turkmenistan","Turks and Caicos Islands","Tuvalu","Uganda","Ukraine","United Arab Emirates","United Kingdom","United States","United States Minor Outlying Islands","Uruguay","Uzbekistan","Vanuatu","Venezuela","Viet Nam","Virgin Islands, British","Virgin Islands, U.S.","Wallis and Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"]; +countries = countries.map((name, index) => { + return { + name: name.toLowerCase(), + id: index, + content: name, + color: color[_.random(0, color.length-1)], + } +}) + +const searchApi = (query, blacklist = []) => { + const data = countries.filter(x => !_.includes(blacklist, x.id)); return new Promise((res,rej) => { setTimeout(function () { - res(data.filter(item => _.includes(item.name, query))); - }, 500); + const result = data.filter(item => _.includes(item.name, query)); + res(result.slice(0, 10)); + }, query == 'brazilq' ? 2000 : 100); }) }; @@ -43,14 +56,16 @@ class DemoPage extends React.Component { this.setState({selectedList: selectedList})}/> + onChange={selectedList => this.setState({selectedList: selectedList})} /> this.setState({selectedList2: selectedList})}/> - + onChange={selectedList => this.setState({selectedList2: selectedList})} /> +
); diff --git a/client/src/core-components/autocomplete-dropdown.js b/client/src/core-components/autocomplete-dropdown.js index 261c5d54..adc44854 100644 --- a/client/src/core-components/autocomplete-dropdown.js +++ b/client/src/core-components/autocomplete-dropdown.js @@ -18,28 +18,38 @@ class AutocompleteDropDown extends React.Component { disabled: React.PropTypes.bool, }; + id = 1; + state = { itemsSelected: [], inputValue: "", opened: false, highlightedIndex: 0, items2: [], + loading: false, }; componentDidMount() { const { getItemListFromQuery, } = this.props; - if (this.state.items2.length === 0){ - if(getItemListFromQuery !== undefined) { - getItemListFromQuery("") - .then(res => { - this.setState({ - items2: res, - }); - }); - } + this.setTimeout = _.throttle((query) => { + let id = ++this.id; + + getItemListFromQuery(query, this.getValue().map(item => item.id)) + .then(res => { + if(id === this.id) + this.setState({ + items2: res, + loading: false, + }); + }) + .catch(() => this.setState({ + loading: false, + })); + }, 300, {leading: false}); + + this.searchApi(""); - } } render() { @@ -64,6 +74,7 @@ class AutocompleteDropDown extends React.Component { opened={this.state.opened} onHighlightedIndexChange={n => this.onHighlightedIndexChange(n)} highlightedIndex={this.state.highlightedIndex} + loading={this.state.loading} > {this.renderSelectedItems()} + return } getDropdownList() { @@ -120,57 +136,46 @@ class AutocompleteDropDown extends React.Component { return (values !== undefined) ? values : this.state.itemsSelected; } - getDeletingItemLoading() { - const { deletingItemLoading, } = this.props; - - return (deletingItemLoading !== undefined) ? deletingItemLoading : false; - } - onRemoveClick(itemId, event) { const { onChange, onRemoveClick, } = this.props; + const newList = this.getValue().filter(item => item.id != itemId); + event.preventDefault(); this.setState({ - itemsSelected: this.getValue().filter(item => item.id != itemId), + itemsSelected: newList, opened: false, highlightedIndex: 0, }); - onChange && onChange(this.getValue().filter(item => item.id != itemId)); - + onChange && onChange(newList); onRemoveClick && onRemoveClick(itemId); + this.searchApi("", newList); } onChangeDropDown(e){ const { onChange, onTagSelected, - getItemListFromQuery, } = this.props; if (this.getDropdownList().length) { + const itemSelected = this.getDropdownList()[e.index]; + const newList = [...this.getValue(), itemSelected]; + this.setState({ - itemsSelected: [...this.getValue(), this.getDropdownList()[e.index]], + itemsSelected: newList, inputValue: "", highlightedIndex: 0, }); - if (getItemListFromQuery !== undefined) { - getItemListFromQuery("") - .then(res => { - this.setState({ - items2: res, - }); - }) - } - - onChange && onChange([...this.getValue(), this.getDropdownList()[e.index]]); - - onTagSelected && onTagSelected(this.getDropdownList()[e.index].id); + onChange && onChange(newList); + onTagSelected && onTagSelected(itemSelected.id); + this.searchApi("", newList); } @@ -186,13 +191,14 @@ class AutocompleteDropDown extends React.Component { }); if (getItemListFromQuery !== undefined) { - getItemListFromQuery(str) - .then(res => { - this.setState({ - items2: res, - }); - }) + this.setState({ + loading: true, + }); + + this.setTimeout(str); + } + } onMenuToggle(b){ @@ -216,26 +222,57 @@ class AutocompleteDropDown extends React.Component { if(this.props.disabled) { event.stopPropagation(); event.preventDefault(); + return; + } if (keyCode(event) === "space"){ event.stopPropagation(); + } if (keyCode(event) === "backspace" && this.state.inputValue === ""){ + const newList = this.getValue().slice(0,this.getValue().length-1); this.setState({ - itemsSelected: this.getValue().slice(0,this.getValue().length-1), + itemsSelected: newList, highlightedIndex: 0, }); - onChange && onChange(this.getValue().slice(0,this.getValue().length-1)); + onChange && onChange(newList); if (this.getValue().length) { const itemId = this.getValue()[this.getValue().length-1].id; + onRemoveClick && onRemoveClick(itemId); } + + this.searchApi("", newList); + } + + } + + searchApi(query, blacklist=this.getValue()) { + const { getItemListFromQuery, } = this.props; + + if (getItemListFromQuery !== undefined) { + + getItemListFromQuery(query, blacklist.map(item => item.id)) + .then(res => { + this.setState({ + items2: res, + loading: false, + }); + }) + .catch(() => { + this.setState({ + loading: false, + }) + }) + + } + } } diff --git a/client/src/core-components/drop-down.js b/client/src/core-components/drop-down.js index 7ff010d7..1ab5b7c8 100644 --- a/client/src/core-components/drop-down.js +++ b/client/src/core-components/drop-down.js @@ -7,6 +7,8 @@ import keyCode from 'keycode'; import Menu from 'core-components/menu'; import Icon from 'core-components/icon'; +import Loading from 'core-components/loading' + class DropDown extends React.Component { static propTypes = { @@ -76,7 +78,11 @@ class DropDown extends React.Component { return (
- + {this.props.loading ? +
: + this.props.items.length ? + : +
Empty
}
); } diff --git a/client/src/core-components/drop-down.scss b/client/src/core-components/drop-down.scss index 7082e708..a56b0760 100644 --- a/client/src/core-components/drop-down.scss +++ b/client/src/core-components/drop-down.scss @@ -28,6 +28,25 @@ position: absolute; width: 150px; z-index: 100; + background-color: white; + height: 40px; + } + + &__loading { + padding-top: 12.5%; + .loading__icon { + border-color: rgba(150, 150, 150, 0.2); + border-left-color: $primary-red; + } + + } + + &__empty-list { + padding: 10px; + background-color: white; + color: $dark-grey; + font-style: italic; + cursor: default; } &_closed { From 780aa64d194383340aac29c46b277b3fee76f5de Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Tue, 14 Jan 2020 17:09:06 -0300 Subject: [PATCH 10/16] Minors changes og line breack and spaces in tagSelector, autocomplete-dropdown and dropdown --- client/src/app/demo/components-demo-page.js | 9 +- .../core-components/autocomplete-dropdown.js | 129 ++++++++---------- .../autocomplete-dropdown.scss | 25 +++- client/src/core-components/drop-down.js | 43 +++--- client/src/core-components/drop-down.scss | 2 +- client/src/core-components/tag-selector.js | 2 +- 6 files changed, 107 insertions(+), 103 deletions(-) diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 72ac03d0..afde6054 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -13,12 +13,12 @@ const color = [ let countries = ["Afghanistan","Åland Islands","Albania","Algeria","American Samoa","AndorrA","Angola","Anguilla","Antarctica","Antigua and Barbuda","Argentina","Armenia","Aruba","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia and Herzegovina","Botswana","Bouvet Island","Brazil","British Indian Ocean Territory","Brunei Darussalam","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Cape Verde","Cayman Islands","Central African Republic","Chad","Chile","China","Christmas Island","Cocos (Keeling) Islands","Colombia","Comoros","Congo","Congo, The Democratic Republic of the","Cook Islands","Costa Rica","Cote D'Ivoire","Croatia","Cuba","Cyprus","Czech Republic","Denmark","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Falkland Islands (Malvinas)","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Heard Island and Mcdonald Islands","Holy See (Vatican City State)","Honduras","Hong Kong","Hungary","Iceland","India","Indonesia","Iran, Islamic Republic Of","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Korea, Democratic People'S Republic of","Korea, Republic of","Kuwait","Kyrgyzstan","Lao People'S Democratic Republic","Latvia","Lebanon","Lesotho","Liberia","Libyan Arab Jamahiriya","Liechtenstein","Lithuania","Luxembourg","Macao","Macedonia, The Former Yugoslav Republic of","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia, Federated States of","Moldova, Republic of","Monaco","Mongolia","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","Netherlands Antilles","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territory, Occupied","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn","Poland","Portugal","Puerto Rico","Qatar","Reunion","Romania","Russian Federation","RWANDA","Saint Helena","Saint Kitts and Nevis","Saint Lucia","Saint Pierre and Miquelon","Saint Vincent and the Grenadines","Samoa","San Marino","Sao Tome and Principe","Saudi Arabia","Senegal","Serbia and Montenegro","Seychelles","Sierra Leone","Singapore","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia and the South Sandwich Islands","Spain","Sri Lanka","Sudan","Suriname","Svalbard and Jan Mayen","Swaziland","Sweden","Switzerland","Syrian Arab Republic","Taiwan, Province of China","Tajikistan","Tanzania, United Republic of","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad and Tobago","Tunisia","Turkey","Turkmenistan","Turks and Caicos Islands","Tuvalu","Uganda","Ukraine","United Arab Emirates","United Kingdom","United States","United States Minor Outlying Islands","Uruguay","Uzbekistan","Vanuatu","Venezuela","Viet Nam","Virgin Islands, British","Virgin Islands, U.S.","Wallis and Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"]; countries = countries.map((name, index) => { return { - name: name.toLowerCase(), id: index, + name: name.toLowerCase(), content: name, color: color[_.random(0, color.length-1)], } -}) +}); const searchApi = (query, blacklist = []) => { const data = countries.filter(x => !_.includes(blacklist, x.id)); @@ -28,7 +28,7 @@ const searchApi = (query, blacklist = []) => { const result = data.filter(item => _.includes(item.name, query)); res(result.slice(0, 10)); }, query == 'brazilq' ? 2000 : 100); - }) + }); }; class DemoPage extends React.Component { @@ -39,7 +39,7 @@ class DemoPage extends React.Component { {id: 48, name: '123123123', content: '123123123.', color: 'blue'}, {id: 49, name: 'hola', content: 'hola', color: 'green'}, ], - selectedList2: [] + selectedList2: [], } render() { @@ -66,7 +66,6 @@ class DemoPage extends React.Component { -
); } diff --git a/client/src/core-components/autocomplete-dropdown.js b/client/src/core-components/autocomplete-dropdown.js index adc44854..ce6e1832 100644 --- a/client/src/core-components/autocomplete-dropdown.js +++ b/client/src/core-components/autocomplete-dropdown.js @@ -1,31 +1,37 @@ -import React, { createRef }from 'react'; +import React from 'react'; import _ from 'lodash'; import keyCode from 'keycode'; import DropDown from 'core-components/drop-down'; -import Menu from 'core-components/menu'; import Tag from 'core-components/tag'; +const ItemsSchema = React.PropTypes.arrayOf(React.PropTypes.shape({ + id: React.PropTypes.number, + name: React.PropTypes.string, + content: React.PropTypes.string, + color: React.PropTypes.string, +})); + class AutocompleteDropDown extends React.Component { static propTypes = { - //items: Menu.propTypes.items, + items: ItemsSchema, onChange: React.PropTypes.func, - values: React.PropTypes.arrayOf(React.PropTypes.shape({})), + values: ItemsSchema, onRemoveClick: React.PropTypes.func, onTagSelected: React.PropTypes.func, - //getItemListFromQuery: React.propTypes.func, + getItemListFromQuery: React.PropTypes.func, disabled: React.PropTypes.bool, }; id = 1; state = { - itemsSelected: [], + selectedItems: [], inputValue: "", opened: false, highlightedIndex: 0, - items2: [], + itemsFromQuery: [], loading: false, }; @@ -35,11 +41,11 @@ class AutocompleteDropDown extends React.Component { this.setTimeout = _.throttle((query) => { let id = ++this.id; - getItemListFromQuery(query, this.getValue().map(item => item.id)) - .then(res => { + getItemListFromQuery(query, this.getSelectedItems().map(item => item.id)) + .then(result => { if(id === this.id) this.setState({ - items2: res, + itemsFromQuery: result, loading: false, }); }) @@ -49,24 +55,23 @@ class AutocompleteDropDown extends React.Component { }, 300, {leading: false}); this.searchApi(""); - } render() { - let inputWith = 0; + let inputWidth = 0; if(this.span) { this.span.style.display = 'inline'; this.span.textContent = this.state.inputValue; - inputWith = Math.ceil(this.span.getBoundingClientRect().width) + 20 + inputWidth = Math.ceil(this.span.getBoundingClientRect().width)-31; this.span.style.display = 'none'; } return ( -
-
); @@ -167,26 +168,26 @@ class DropDown extends React.Component { return { 'up': () => { if (opened) { + const newHighlightedIndex = this.modulo(highlightedIndex - 1, itemsQuantity); event.preventDefault(); this.setState({ - highlightedIndex: this.modulo(highlightedIndex - 1, itemsQuantity) + highlightedIndex: newHighlightedIndex, }); - onHighlightedIndexChange && onHighlightedIndexChange(this.modulo(highlightedIndex - 1, itemsQuantity)); - + onHighlightedIndexChange && onHighlightedIndexChange(newHighlightedIndex); } }, 'down': () => { if (opened) { + const newHighlightedIndex = this.modulo(highlightedIndex + 1, itemsQuantity); event.preventDefault(); this.setState({ - highlightedIndex: this.modulo(highlightedIndex + 1, itemsQuantity) + highlightedIndex: newHighlightedIndex, }); - onHighlightedIndexChange && onHighlightedIndexChange(this.modulo(highlightedIndex + 1, itemsQuantity)); - + onHighlightedIndexChange && onHighlightedIndexChange(newHighlightedIndex); } }, 'enter': () => { @@ -198,7 +199,6 @@ class DropDown extends React.Component { }); onMenuToggle && onMenuToggle(true); - } }, 'space': () => { @@ -209,7 +209,6 @@ class DropDown extends React.Component { }); onMenuToggle && onMenuToggle(true); - }, 'esc': () => { this.setState({ @@ -217,14 +216,12 @@ class DropDown extends React.Component { }); onMenuToggle && onMenuToggle(false); - }, 'tab': () => { if (this.getOpen()) { event.preventDefault(); onHighlightedIndexChange && this.onIndexSelected(highlightedIndex); - } } }; @@ -238,7 +235,6 @@ class DropDown extends React.Component { }); onMenuToggle && onMenuToggle(false); - } handleClick() { @@ -249,7 +245,6 @@ class DropDown extends React.Component { }); onMenuToggle && onMenuToggle(!this.getOpen()); - } handleItemClick(index) { @@ -268,13 +263,12 @@ class DropDown extends React.Component { selectedIndex: index, highlightedIndex: index }); - + onHighlightedIndexChange && onHighlightedIndexChange(index); - + onMenuToggle && onMenuToggle(false); onChange && onChange({ index }); - } handleListMouseDown(event) { @@ -291,23 +285,26 @@ class DropDown extends React.Component { }); onHighlightedIndexChange && onHighlightedIndexChange(this.getSelectedIndex()); - } } getSelectedIndex() { - return (this.props.selectedIndex !== undefined) ? this.props.selectedIndex : this.state.selectedIndex; + const { selectedIndex, } = this.props; + return (selectedIndex !== undefined) ? selectedIndex : this.state.selectedIndex; } + modulo(number, mod) { return ((number % mod) + mod) % mod; } getOpen(){ - return (this.props.opened !== undefined) ? this.props.opened : this.state.opened; + const { opened, } = this.props; + return (opened !== undefined) ? opened : this.state.opened; } getHighlightedIndex() { - return (this.props.highlightedIndex !== undefined) ? this.props.highlightedIndex : this.state.highlightedIndex; + const { highlightedIndex, } = this.props; + return (highlightedIndex !== undefined) ? highlightedIndex : this.state.highlightedIndex; } } diff --git a/client/src/core-components/drop-down.scss b/client/src/core-components/drop-down.scss index a56b0760..da617635 100644 --- a/client/src/core-components/drop-down.scss +++ b/client/src/core-components/drop-down.scss @@ -34,11 +34,11 @@ &__loading { padding-top: 12.5%; + .loading__icon { border-color: rgba(150, 150, 150, 0.2); border-left-color: $primary-red; } - } &__empty-list { diff --git a/client/src/core-components/tag-selector.js b/client/src/core-components/tag-selector.js index 25607a2e..6e0f5032 100644 --- a/client/src/core-components/tag-selector.js +++ b/client/src/core-components/tag-selector.js @@ -17,7 +17,7 @@ class TagSelector extends React.Component { render() { const items = this.props.items.map(tag => ({...tag, content: this.renderTagOption(tag)})); - const values = items.filter(item => _.includes(this.props.values, item.name )); + const values = items.filter(item => _.includes(this.props.values, item.name)); return (
From 0dd8e7524158d8d8348b3a75de04d05b77036f89 Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Thu, 16 Jan 2020 14:26:01 -0300 Subject: [PATCH 11/16] Add autocomplete test --- client/src/app/demo/components-demo-page.js | 12 +- .../__tests__/autocomplete-test.js | 174 +++++++++++ client/src/core-components/autocomplete.js | 269 ++++++++++++++++++ client/src/core-components/autocomplete.scss | 42 +++ client/src/core-components/tag-selector.js | 4 +- 5 files changed, 495 insertions(+), 6 deletions(-) create mode 100644 client/src/core-components/__tests__/autocomplete-test.js create mode 100644 client/src/core-components/autocomplete.js create mode 100644 client/src/core-components/autocomplete.scss diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index afde6054..22220662 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -1,4 +1,4 @@ -import AutocompleteDropDown from 'core-components/autocomplete-dropdown'; +import Autocomplete from 'core-components/autocomplete'; const React = require('react'); const _ = require('lodash'); @@ -27,7 +27,7 @@ const searchApi = (query, blacklist = []) => { setTimeout(function () { const result = data.filter(item => _.includes(item.name, query)); res(result.slice(0, 10)); - }, query == 'brazilq' ? 2000 : 100); + }, query == 'brazilq' ? 100 : 50); }); }; @@ -53,13 +53,17 @@ class DemoPage extends React.Component { return (
- this.setState({selectedList: selectedList})} /> - this.setState({selectedList2: selectedList})} /> + + this.setState({selectedList2: selectedList})} /> diff --git a/client/src/core-components/__tests__/autocomplete-test.js b/client/src/core-components/__tests__/autocomplete-test.js new file mode 100644 index 00000000..c2b8bca7 --- /dev/null +++ b/client/src/core-components/__tests__/autocomplete-test.js @@ -0,0 +1,174 @@ +// LIBS +const _ = require('lodash'); + +// MOCKS +const Tag = ReactMock(); +const DropDown = ReactMock(); + +// COMPONENT +const Autocomplete = requireUnit('core-components/autocomplete', { + 'core-components/drop-down': DropDown, + 'core-components/tag': Tag, +}); + + +const color = [ + 'red', + 'cyan', + 'blue', + 'green', +]; + +let countries = ["Afghanistan","Åland Islands","Albania","Algeria","American Samoa","AndorrA","Angola","Anguilla","Antarctica","Antigua and Barbuda","Argentina","Armenia","Aruba","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia and Herzegovina","Botswana","Bouvet Island","Brazil","British Indian Ocean Territory","Brunei Darussalam","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Cape Verde","Cayman Islands","Central African Republic","Chad","Chile","China","Christmas Island","Cocos (Keeling) Islands","Colombia","Comoros","Congo","Congo, The Democratic Republic of the","Cook Islands","Costa Rica","Cote D'Ivoire","Croatia","Cuba","Cyprus","Czech Republic","Denmark","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Falkland Islands (Malvinas)","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Heard Island and Mcdonald Islands","Holy See (Vatican City State)","Honduras","Hong Kong","Hungary","Iceland","India","Indonesia","Iran, Islamic Republic Of","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Korea, Democratic People'S Republic of","Korea, Republic of","Kuwait","Kyrgyzstan","Lao People'S Democratic Republic","Latvia","Lebanon","Lesotho","Liberia","Libyan Arab Jamahiriya","Liechtenstein","Lithuania","Luxembourg","Macao","Macedonia, The Former Yugoslav Republic of","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia, Federated States of","Moldova, Republic of","Monaco","Mongolia","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","Netherlands Antilles","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territory, Occupied","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn","Poland","Portugal","Puerto Rico","Qatar","Reunion","Romania","Russian Federation","RWANDA","Saint Helena","Saint Kitts and Nevis","Saint Lucia","Saint Pierre and Miquelon","Saint Vincent and the Grenadines","Samoa","San Marino","Sao Tome and Principe","Saudi Arabia","Senegal","Serbia and Montenegro","Seychelles","Sierra Leone","Singapore","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia and the South Sandwich Islands","Spain","Sri Lanka","Sudan","Suriname","Svalbard and Jan Mayen","Swaziland","Sweden","Switzerland","Syrian Arab Republic","Taiwan, Province of China","Tajikistan","Tanzania, United Republic of","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad and Tobago","Tunisia","Turkey","Turkmenistan","Turks and Caicos Islands","Tuvalu","Uganda","Ukraine","United Arab Emirates","United Kingdom","United States","United States Minor Outlying Islands","Uruguay","Uzbekistan","Vanuatu","Venezuela","Viet Nam","Virgin Islands, British","Virgin Islands, U.S.","Wallis and Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"]; +countries = countries.map((name, index) => { + return { + id: index, + name: name.toLowerCase(), + content: name, + color: color[_.random(0, color.length-1)], + } +}); + +const timeout = function (f, t) { + return new Promise(function (res) { + setTimeout(function() { + res(f()); + },t); + }); +}; + +const searchApi = spy((query, blacklist = []) => { + const data = countries.filter(x => !_.includes(blacklist, x.id)); + + return new Promise((res,rej) => { + setTimeout(function () { + const result = data.filter(item => _.includes(item.name, query)); + res(result.slice(0, 10)); + }, query == 'brazilq' ? 100 : 50); + }); +}); + +describe('Autocomplete component with external api', function () { + let selectedList2 = []; + + let autocompleteWithExternalApi = TestUtils.renderIntoDocument( + selectedList2 = selectedList} /> + ); + + let autocompleteInput = TestUtils.scryRenderedDOMComponentsWithClass(autocompleteWithExternalApi, 'autocomplete__input')[0]; + let autocompleteDropdown = TestUtils.scryRenderedComponentsWithType(autocompleteWithExternalApi, DropDown)[0]; + + describe('writing in input', function() { + expect(searchApi).to.have.been.calledWith(); + it('should open menu with list', function() { + autocompleteInput.value = "ho"; + TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + return timeout(function () { + expect(autocompleteDropdown.props.loading).to.equal(false); + expect(autocompleteDropdown.props.items.length).to.equal(4); + }, 360).then(function () { + autocompleteDropdown.props.onMenuToggle(false); + expect(autocompleteDropdown.props.opened).to.equal(false); + }); + }); + it('should select item if enter is pressed', function() { + selectedList2 = []; + autocompleteInput.value = "argentina"; + TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + return timeout(function () { + expect(autocompleteDropdown.props.loading).to.equal(false); + expect(autocompleteDropdown.props.items.length).to.equal(1); + expect(selectedList2.length).to.equal(0); + autocompleteDropdown.props.onChange({index:0}); + expect(selectedList2.length).to.equal(1); + expect(autocompleteDropdown.props.opened).to.equal(false); + }, 360) + }); + + it("should sinc", function() { + selectedList2 = []; + autocompleteInput.value = "brazilq"; + TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + return timeout(function () { + autocompleteInput.value = "brazil"; + TestUtils.Simulate.change(autocompleteInput); + return timeout(function () { + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(1); + expect(selectedList2.length).to.equal(0); + autocompleteDropdown.props.onChange({index:0}); + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(selectedList2.length).to.equal(2); + expect(selectedList2[0].name).to.equal("argentina"); + expect(selectedList2[1].name).to.equal("brazil"); + autocompleteDropdown.props.onMenuToggle(true); + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(0); + }, 360); + }, 50); + }); + + it("should delete item if backspace is pressed and input value is '' ", function() { + autocompleteInput.value = "Z"; + TestUtils.Simulate.change(autocompleteInput); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + expect(selectedList2.length).to.equal(2); + autocompleteInput.value = ""; + TestUtils.Simulate.change(autocompleteInput); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + expect(selectedList2.length).to.equal(1); + expect(selectedList2[0].name).to.equal("argentina"); + + }); + }); + + it('123123123', function() { + autocompleteInput.value = "ho"; + TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + + return timeout(function () { + expect(autocompleteDropdown.props.loading).to.equal(false); + expect(autocompleteDropdown.props.items.length).to.equal(4); + }, 360).then(function () { + autocompleteDropdown.props.onMenuToggle(false); + expect(autocompleteDropdown.props.opened).to.equal(false); + + autocompleteWithExternalApi.props.onChange([...selectedList2, {asd: "asd"}, ]); + expect(selectedList2.length).to.equal(1); + + autocompleteWithExternalApi.props.onChange([...selectedList2, {asd: "asd"}, ]); + expect(selectedList2.length).to.equal(2); + + autocompleteWithExternalApi.props.onChange(selectedList2.slice(0, selectedList2.length-1)); + expect(selectedList2.length).to.equal(1); + + autocompleteInput.value = "123"; + TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + + return timeout(function () { + expect(autocompleteDropdown.props.loading).to.equal(false); + expect(autocompleteDropdown.props.items.length).to.equal(0); + }, 360).then(function () { + autocompleteDropdown.props.onMenuToggle(false); + expect(autocompleteDropdown.props.opened).to.equal(false); + + autocompleteWithExternalApi.props.onChange(selectedList2); + expect(selectedList2.length).to.equal(1); + + }); + //expect(1 == 2).to.equal(true); + }); + }); +}); diff --git a/client/src/core-components/autocomplete.js b/client/src/core-components/autocomplete.js new file mode 100644 index 00000000..7c27cd9d --- /dev/null +++ b/client/src/core-components/autocomplete.js @@ -0,0 +1,269 @@ +import React from 'react'; +import _ from 'lodash'; +import keyCode from 'keycode'; + +import DropDown from 'core-components/drop-down'; +import Tag from 'core-components/tag'; + +const ItemsSchema = React.PropTypes.arrayOf(React.PropTypes.shape({ + id: React.PropTypes.number, + name: React.PropTypes.string, + content: React.PropTypes.string, + color: React.PropTypes.string, +})); + +class Autocomplete extends React.Component { + + static propTypes = { + items: ItemsSchema, + onChange: React.PropTypes.func, + values: ItemsSchema, + onRemoveClick: React.PropTypes.func, + onTagSelected: React.PropTypes.func, + getItemListFromQuery: React.PropTypes.func, + disabled: React.PropTypes.bool, + }; + + id = 1; + + state = { + selectedItems: [], + inputValue: "", + opened: false, + highlightedIndex: 0, + itemsFromQuery: [], + loading: false, + }; + + componentDidMount() { + const { getItemListFromQuery, } = this.props; + + this.setTimeout = _.throttle((query) => { + let id = ++this.id; + + getItemListFromQuery(query, this.getSelectedItems().map(item => item.id)) + .then(result => { + if(id === this.id) + this.setState({ + itemsFromQuery: result, + loading: false, + }); + }) + .catch(() => this.setState({ + loading: false, + })); + }, 300, {leading: false}); + + this.searchApi(""); + } + + render() { + let inputWidth = 0; + + if(this.span) { + this.span.style.display = 'inline'; + this.span.textContent = this.state.inputValue; + inputWidth = Math.ceil(this.span.getBoundingClientRect().width)-31; + this.span.style.display = 'none'; + } + + return ( +
+ +
+ ); + } + + renderSelectedItems() { + return this.getSelectedItems().map(item => this.renderSelectedItem(item)); + } + + renderSelectedItem(item) { + return + } + + getDropdownList() { + const { + items, + } = this.props; + let dropdownList = []; + + if(items !== undefined) { + const list = this.getUnselectedList(items, this.getSelectedItems()); + + dropdownList = list.filter(s => _.includes(s.name, this.state.inputValue)); + } else { + dropdownList = this.getUnselectedList(this.state.itemsFromQuery, this.getSelectedItems()); + } + + return dropdownList; + } + + getUnselectedList(list, selectedList) { + return list.filter(item => !_.some(selectedList, item)); + } + + getSelectedItems() { + const { values, } = this.props; + + return (values !== undefined) ? values : this.state.selectedItems; + } + + onRemoveClick(itemId, event) { + const { + onChange, + onRemoveClick, + } = this.props; + const newList = this.getSelectedItems().filter(item => item.id != itemId); + event.preventDefault(); + + this.setState({ + selectedItems: newList, + opened: false, + highlightedIndex: 0, + }); + + onChange && onChange(newList); + onRemoveClick && onRemoveClick(itemId); + this.searchApi("", newList); + } + + onChangeDropDown(e) { + const { + onChange, + onTagSelected, + } = this.props; + + if(this.getDropdownList().length) { + console.log("asdsad"); + const itemSelected = this.getDropdownList()[e.index]; + const newList = [...this.getSelectedItems(), itemSelected]; + + this.setState({ + selectedItems: newList, + inputValue: "", + highlightedIndex: 0, + opened: false, + }); + + onChange && onChange(newList); + onTagSelected && onTagSelected(itemSelected.id); + this.searchApi("", newList); + } + } + + onChangeInput(str) { + const { getItemListFromQuery, } = this.props; + + this.setState({ + inputValue: str, + opened: true, + highlightedIndex: 0, + }); + + if(getItemListFromQuery !== undefined) { + this.setState({ + loading: true, + }); + + this.setTimeout(str); + } + } + + onMenuToggle(e) { + this.setState({ + opened: e, + }); + } + + onHighlightedIndexChange(n) { + this.setState({ + highlightedIndex: n, + }); + } + + onKeyDown(event) { + const { + onChange, + onRemoveClick, + } = this.props; + + if(this.props.disabled) { + event.stopPropagation(); + event.preventDefault(); + + return; + } + + if(keyCode(event) === "space") { + event.stopPropagation(); + } + + if(keyCode(event) === "backspace" && this.state.inputValue === "") { + const lastSelectedItemsIndex = this.getSelectedItems().length-1; + const newList = this.getSelectedItems().slice(0, lastSelectedItemsIndex); + this.setState({ + selectedItems: newList, + highlightedIndex: 0, + }); + onChange && onChange(newList); + + if(this.getSelectedItems().length) { + const itemId = this.getSelectedItems()[lastSelectedItemsIndex].id; + + onRemoveClick && onRemoveClick(itemId); + } + + this.searchApi("", newList); + } + } + + searchApi(query, blacklist=this.getSelectedItems()) { + const { getItemListFromQuery, } = this.props; + + if(getItemListFromQuery !== undefined) { + getItemListFromQuery(query, blacklist.map(item => item.id)) + .then(result => { + this.setState({ + itemsFromQuery: result, + loading: false, + }); + }) + .catch(() => { + this.setState({ + loading: false, + }); + }); + } + } +} + +export default Autocomplete; diff --git a/client/src/core-components/autocomplete.scss b/client/src/core-components/autocomplete.scss new file mode 100644 index 00000000..46a9af7e --- /dev/null +++ b/client/src/core-components/autocomplete.scss @@ -0,0 +1,42 @@ +@import "../scss/vars"; + +.autocomplete { + margin-bottom: 30px; + text-align: left; + + &__drop-down { + .drop-down__current-item { + cursor: text; + background-color: $very-light-grey; + border: 1px solid $grey; + min-height: 38px; + + &:focus { + outline: none; + border-color: $primary-blue; + } + } + } + + &__label { + display: inline-block; + } + + &__input { + display: inline-block; + border: 0; + background: transparent; + outline: none; + padding-left: 5px; + width: 10px; + max-width: 100%; + min-width: 10px; + } + + .sizer { + line-height: 1.21428571em; + padding: .67857143em 2.1em .67857143em 1em; + display: none; + white-space: pre; + } +} diff --git a/client/src/core-components/tag-selector.js b/client/src/core-components/tag-selector.js index 6e0f5032..55062cde 100644 --- a/client/src/core-components/tag-selector.js +++ b/client/src/core-components/tag-selector.js @@ -1,6 +1,6 @@ import React from 'react'; import _ from 'lodash'; -import AutocompleteDropDown from './autocomplete-dropdown'; +import Autocomplete from 'core-components/autocomplete'; class TagSelector extends React.Component { @@ -21,7 +21,7 @@ class TagSelector extends React.Component { return (
- Date: Fri, 17 Jan 2020 20:41:44 -0300 Subject: [PATCH 12/16] Add autocomplete test part two --- client/src/app/demo/components-demo-page.js | 4 - .../__tests__/autocomplete-test.js | 160 ++++++++++-------- client/src/core-components/autocomplete.js | 1 - client/src/core-components/drop-down.js | 19 ++- 4 files changed, 104 insertions(+), 80 deletions(-) diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 22220662..27ceb038 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -59,10 +59,6 @@ class DemoPage extends React.Component { onChange={selectedList => this.setState({selectedList: selectedList})} /> - this.setState({selectedList2: selectedList})} /> - { }); describe('Autocomplete component with external api', function () { - let selectedList2 = []; + let selectedList2 = [], autocompleteInput, autocompleteDropdown; + function renderAutocomplete(props) { + selectedList2 = []; - let autocompleteWithExternalApi = TestUtils.renderIntoDocument( - selectedList2 = selectedList} /> - ); + let autocompleteWithExternalApi = TestUtils.renderIntoDocument( + selectedList2 = selectedList} /> + ); - let autocompleteInput = TestUtils.scryRenderedDOMComponentsWithClass(autocompleteWithExternalApi, 'autocomplete__input')[0]; - let autocompleteDropdown = TestUtils.scryRenderedComponentsWithType(autocompleteWithExternalApi, DropDown)[0]; + autocompleteInput = TestUtils.scryRenderedDOMComponentsWithClass(autocompleteWithExternalApi, 'autocomplete__input')[0]; + autocompleteDropdown = TestUtils.scryRenderedComponentsWithType(autocompleteWithExternalApi, DropDown)[0]; + } describe('writing in input', function() { - expect(searchApi).to.have.been.calledWith(); + beforeEach(function() { + renderAutocomplete(); + }); + it('should open menu with list', function() { + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); autocompleteInput.value = "ho"; TestUtils.Simulate.change(autocompleteInput); expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.loading).to.equal(true); return timeout(function () { + expect(searchApi).to.have.been.calledWith("ho", selectedList2.map(item => item.id)); expect(autocompleteDropdown.props.loading).to.equal(false); expect(autocompleteDropdown.props.items.length).to.equal(4); - }, 360).then(function () { - autocompleteDropdown.props.onMenuToggle(false); + expect(selectedList2.length).to.equal(0); + autocompleteDropdown.props.onChange({index: 1}); + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + expect(selectedList2[0].name).to.equal("honduras"); expect(autocompleteDropdown.props.opened).to.equal(false); - }); + }, 360); }); + it('should select item if enter is pressed', function() { - selectedList2 = []; + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); autocompleteInput.value = "argentina"; TestUtils.Simulate.change(autocompleteInput); expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.loading).to.equal(true); return timeout(function () { + expect(searchApi).to.have.been.calledWith("argentina", selectedList2.map(item => item.id)); expect(autocompleteDropdown.props.loading).to.equal(false); expect(autocompleteDropdown.props.items.length).to.equal(1); expect(selectedList2.length).to.equal(0); - autocompleteDropdown.props.onChange({index:0}); + autocompleteDropdown.props.onChange({index: 0}); + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); expect(selectedList2.length).to.equal(1); expect(autocompleteDropdown.props.opened).to.equal(false); - }, 360) + }, 360); }); it("should sinc", function() { - selectedList2 = []; + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); autocompleteInput.value = "brazilq"; TestUtils.Simulate.change(autocompleteInput); expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.loading).to.equal(true); return timeout(function () { + expect(searchApi).to.have.not.been.calledWith("brazil", selectedList2.map(item => item.id)); autocompleteInput.value = "brazil"; TestUtils.Simulate.change(autocompleteInput); return timeout(function () { + expect(searchApi).to.have.been.calledWith("brazil", selectedList2.map(item => item.id)); expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.items.length).to.equal(1); expect(selectedList2.length).to.equal(0); - autocompleteDropdown.props.onChange({index:0}); + autocompleteDropdown.props.onChange({index: 0}); + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); expect(autocompleteDropdown.props.opened).to.equal(false); - expect(selectedList2.length).to.equal(2); - expect(selectedList2[0].name).to.equal("argentina"); - expect(selectedList2[1].name).to.equal("brazil"); + expect(selectedList2.length).to.equal(1); + expect(selectedList2[0].name).to.equal("brazil"); autocompleteDropdown.props.onMenuToggle(true); expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.items.length).to.equal(0); @@ -117,58 +132,69 @@ describe('Autocomplete component with external api', function () { }); it("should delete item if backspace is pressed and input value is '' ", function() { - autocompleteInput.value = "Z"; - TestUtils.Simulate.change(autocompleteInput); - TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); - expect(selectedList2.length).to.equal(2); - autocompleteInput.value = ""; - TestUtils.Simulate.change(autocompleteInput); - TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); - expect(selectedList2.length).to.equal(1); - expect(selectedList2[0].name).to.equal("argentina"); - - }); - }); - - it('123123123', function() { - autocompleteInput.value = "ho"; - TestUtils.Simulate.change(autocompleteInput); - expect(autocompleteDropdown.props.opened).to.equal(true); - expect(autocompleteDropdown.props.loading).to.equal(true); - - return timeout(function () { - expect(autocompleteDropdown.props.loading).to.equal(false); - expect(autocompleteDropdown.props.items.length).to.equal(4); - }, 360).then(function () { - autocompleteDropdown.props.onMenuToggle(false); - expect(autocompleteDropdown.props.opened).to.equal(false); - - autocompleteWithExternalApi.props.onChange([...selectedList2, {asd: "asd"}, ]); - expect(selectedList2.length).to.equal(1); - - autocompleteWithExternalApi.props.onChange([...selectedList2, {asd: "asd"}, ]); - expect(selectedList2.length).to.equal(2); - - autocompleteWithExternalApi.props.onChange(selectedList2.slice(0, selectedList2.length-1)); - expect(selectedList2.length).to.equal(1); - - autocompleteInput.value = "123"; + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + autocompleteInput.value = "za"; TestUtils.Simulate.change(autocompleteInput); expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.loading).to.equal(true); - return timeout(function () { - expect(autocompleteDropdown.props.loading).to.equal(false); - expect(autocompleteDropdown.props.items.length).to.equal(0); - }, 360).then(function () { - autocompleteDropdown.props.onMenuToggle(false); - expect(autocompleteDropdown.props.opened).to.equal(false); - - autocompleteWithExternalApi.props.onChange(selectedList2); + expect(searchApi).to.have.been.calledWith("za", selectedList2.map(item => item.id)); + autocompleteDropdown.props.onChange({index: 3}); + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + expect(selectedList2[0].name).to.equal("zambia"); expect(selectedList2.length).to.equal(1); - - }); - //expect(1 == 2).to.equal(true); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + expect(selectedList2.length).to.equal(0); + autocompleteInput.value = ""; + TestUtils.Simulate.change(autocompleteInput); + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + expect(selectedList2.length).to.equal(0); + }, 360); }); + + it("should ...", function() { + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + autocompleteInput.value = "ang"; + TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + return timeout(function () { + expect(searchApi).to.have.been.calledWith("ang", selectedList2.map(item => item.id)); + expect(autocompleteDropdown.props.items.length).to.equal(3); + autocompleteDropdown.props.onChange({index: 0}); + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + expect(selectedList2[0].name).to.equal("angola"); + expect(selectedList2.length).to.equal(1); + autocompleteInput.value = "ang"; + TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + return timeout(function() { + expect(searchApi).to.have.been.calledWith("ang", selectedList2.map(item => item.id)); + expect(autocompleteDropdown.props.items.length).to.equal(2); + autocompleteDropdown.props.onChange({index: 0}); + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + expect(selectedList2.length).to.equal(2); + expect(selectedList2[1].name).to.equal("anguilla"); + expect(autocompleteDropdown.props.items.length).to.equal(1); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + expect(selectedList2.length).to.equal(1); + expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + autocompleteInput.value = "ang"; + TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + return timeout(function() { + expect(searchApi).to.have.been.calledWith("ang", selectedList2.map(item => item.id)); + expect(autocompleteDropdown.props.items.length).to.equal(2); + expect(autocompleteDropdown.props.items[0].name).to.equal("anguilla"); + }, 360); + },360); + }, 360); + }); + }); }); diff --git a/client/src/core-components/autocomplete.js b/client/src/core-components/autocomplete.js index 7c27cd9d..1903a13a 100644 --- a/client/src/core-components/autocomplete.js +++ b/client/src/core-components/autocomplete.js @@ -163,7 +163,6 @@ class Autocomplete extends React.Component { } = this.props; if(this.getDropdownList().length) { - console.log("asdsad"); const itemSelected = this.getDropdownList()[e.index]; const newList = [...this.getSelectedItems(), itemSelected]; diff --git a/client/src/core-components/drop-down.js b/client/src/core-components/drop-down.js index 9fdcb3b7..176ea124 100644 --- a/client/src/core-components/drop-down.js +++ b/client/src/core-components/drop-down.js @@ -256,19 +256,22 @@ class DropDown extends React.Component { onMenuToggle, onHighlightedIndexChange, onChange, + loading, } = this.props; - this.setState({ - opened: false, - selectedIndex: index, - highlightedIndex: index - }); + if (!loading){ + this.setState({ + opened: false, + selectedIndex: index, + highlightedIndex: index + }); - onHighlightedIndexChange && onHighlightedIndexChange(index); + onHighlightedIndexChange && onHighlightedIndexChange(index); - onMenuToggle && onMenuToggle(false); + onMenuToggle && onMenuToggle(false); - onChange && onChange({ index }); + onChange && onChange({ index }); + } } handleListMouseDown(event) { From e63809400ca6a7bb8a9af99cee0b2875d2af49a5 Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Mon, 20 Jan 2020 15:46:06 -0300 Subject: [PATCH 13/16] Add autocomplete test part three --- client/src/app/demo/components-demo-page.js | 20 +- .../__tests__/autocomplete-test.js | 440 +++++++++++++++--- .../core-components/autocomplete-dropdown.js | 267 ----------- .../autocomplete-dropdown.scss | 42 -- 4 files changed, 381 insertions(+), 388 deletions(-) delete mode 100644 client/src/core-components/autocomplete-dropdown.js delete mode 100644 client/src/core-components/autocomplete-dropdown.scss diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 27ceb038..06d98c54 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -44,7 +44,7 @@ class DemoPage extends React.Component { render() { let itemsList = [ - {id: 45, name: 'Lautaro', content: 'Lautaro.', color: 'gray'}, + {id: 45, name: 'lautaro', content: 'Lautaro.', color: 'gray'}, {id: 46, name: 'dsafa', content: 'dsafa', color: 'black'}, {id: 47, name: 'asdasdasd', content: 'asdasdasd.', color: 'red'}, {id: 48, name: '123123123', content: '123123123.', color: 'blue'}, @@ -54,15 +54,17 @@ class DemoPage extends React.Component { return (
this.setState({selectedList: selectedList})} /> - - + items={itemsList} + values={this.state.selectedList} + onChange={selectedList => this.setState({selectedList: selectedList})} /> + this.setState({selectedList2: selectedList})} /> + values={this.state.selectedList2} + getItemListFromQuery={searchApi} + onChange={selectedList => this.setState({selectedList2: selectedList})} /> diff --git a/client/src/core-components/__tests__/autocomplete-test.js b/client/src/core-components/__tests__/autocomplete-test.js index 2e0fb567..bd8d7807 100644 --- a/client/src/core-components/__tests__/autocomplete-test.js +++ b/client/src/core-components/__tests__/autocomplete-test.js @@ -49,14 +49,14 @@ const searchApi = spy((query, blacklist = []) => { }); describe('Autocomplete component with external api', function () { - let selectedList2 = [], autocompleteInput, autocompleteDropdown; + let selectedList = [], autocompleteInput, autocompleteDropdown, autocompleteWithExternalApi, tag; function renderAutocomplete(props) { - selectedList2 = []; + selectedList = []; - let autocompleteWithExternalApi = TestUtils.renderIntoDocument( + autocompleteWithExternalApi = TestUtils.renderIntoDocument( selectedList2 = selectedList} /> + onChange={selectedListAutocomplete => selectedList = selectedListAutocomplete} /> ); autocompleteInput = TestUtils.scryRenderedDOMComponentsWithClass(autocompleteWithExternalApi, 'autocomplete__input')[0]; @@ -69,132 +69,432 @@ describe('Autocomplete component with external api', function () { }); it('should open menu with list', function() { - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(0); + autocompleteInput.value = "ho"; TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.loading).to.equal(true); - return timeout(function () { - expect(searchApi).to.have.been.calledWith("ho", selectedList2.map(item => item.id)); - expect(autocompleteDropdown.props.loading).to.equal(false); - expect(autocompleteDropdown.props.items.length).to.equal(4); - expect(selectedList2.length).to.equal(0); - autocompleteDropdown.props.onChange({index: 1}); - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); - expect(selectedList2[0].name).to.equal("honduras"); - expect(autocompleteDropdown.props.opened).to.equal(false); - }, 360); + expect(selectedList.length).to.equal(0); }); it('should select item if enter is pressed', function() { - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(0); + autocompleteInput.value = "argentina"; TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.loading).to.equal(true); - return timeout(function () { - expect(searchApi).to.have.been.calledWith("argentina", selectedList2.map(item => item.id)); + + return timeout(function() { expect(autocompleteDropdown.props.loading).to.equal(false); + expect(searchApi).to.have.been.calledWith("argentina", selectedList.map(item => item.id)); expect(autocompleteDropdown.props.items.length).to.equal(1); - expect(selectedList2.length).to.equal(0); + expect(autocompleteDropdown.props.items[0].name).to.equal("argentina"); + expect(autocompleteDropdown.props.items[0].id).to.equal(10); + expect(selectedList.length).to.equal(0); + autocompleteDropdown.props.onChange({index: 0}); - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); - expect(selectedList2.length).to.equal(1); + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(1); + expect(selectedList[0].name).to.equal("argentina"); + expect(selectedList[0].id).to.equal(10); }, 360); }); - it("should sinc", function() { - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + it('should sinc', function() { + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(0); + autocompleteInput.value = "brazilq"; TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.loading).to.equal(true); - return timeout(function () { - expect(searchApi).to.have.not.been.calledWith("brazil", selectedList2.map(item => item.id)); + + return timeout(function() { + expect(autocompleteDropdown.props.loading).to.equal(true); + expect(searchApi).to.have.not.been.calledWith("brazil", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(0); + autocompleteInput.value = "brazil"; TestUtils.Simulate.change(autocompleteInput); - return timeout(function () { - expect(searchApi).to.have.been.calledWith("brazil", selectedList2.map(item => item.id)); - expect(autocompleteDropdown.props.opened).to.equal(true); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + + return timeout(function() { + expect(autocompleteDropdown.props.loading).to.equal(false); + expect(searchApi).to.have.been.calledWith("brazil", selectedList.map(item => item.id)); expect(autocompleteDropdown.props.items.length).to.equal(1); - expect(selectedList2.length).to.equal(0); + expect(autocompleteDropdown.props.items[0].name).to.equal("brazil"); + expect(autocompleteDropdown.props.items[0].id).to.equal(30); + expect(selectedList.length).to.equal(0); + autocompleteDropdown.props.onChange({index: 0}); - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + expect(autocompleteDropdown.props.opened).to.equal(false); - expect(selectedList2.length).to.equal(1); - expect(selectedList2[0].name).to.equal("brazil"); + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(selectedList.length).to.equal(1); + expect(selectedList[0].name).to.equal("brazil"); + expect(selectedList[0].id).to.equal(30); + autocompleteDropdown.props.onMenuToggle(true); + expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.items.length).to.equal(0); }, 360); - }, 50); + }, 25); }); - it("should delete item if backspace is pressed and input value is '' ", function() { - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); - autocompleteInput.value = "za"; - TestUtils.Simulate.change(autocompleteInput); - expect(autocompleteDropdown.props.opened).to.equal(true); - expect(autocompleteDropdown.props.loading).to.equal(true); - return timeout(function () { - expect(searchApi).to.have.been.calledWith("za", selectedList2.map(item => item.id)); - autocompleteDropdown.props.onChange({index: 3}); - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); - expect(selectedList2[0].name).to.equal("zambia"); - expect(selectedList2.length).to.equal(1); - TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); - expect(selectedList2.length).to.equal(0); - autocompleteInput.value = ""; - TestUtils.Simulate.change(autocompleteInput); - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); - TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); - expect(selectedList2.length).to.equal(0); - }, 360); - }); + it('should delete item if backspace is pressed and input value is ""', function() { + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(0); - it("should ...", function() { - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); autocompleteInput.value = "ang"; TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.loading).to.equal(true); - return timeout(function () { - expect(searchApi).to.have.been.calledWith("ang", selectedList2.map(item => item.id)); + + return timeout(function() { + expect(autocompleteDropdown.props.loading).to.equal(false); + expect(searchApi).to.have.been.calledWith("ang", selectedList.map(item => item.id)); expect(autocompleteDropdown.props.items.length).to.equal(3); + expect(autocompleteDropdown.props.items[0].name).to.equal("angola"); + expect(autocompleteDropdown.props.items[0].id).to.equal(6); + expect(selectedList.length).to.equal(0); + autocompleteDropdown.props.onChange({index: 0}); - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); - expect(selectedList2[0].name).to.equal("angola"); - expect(selectedList2.length).to.equal(1); + + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(1); + expect(selectedList[0].name).to.equal("angola"); + expect(selectedList[0].id).to.equal(6); + autocompleteInput.value = "ang"; TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.loading).to.equal(true); + return timeout(function() { - expect(searchApi).to.have.been.calledWith("ang", selectedList2.map(item => item.id)); + expect(autocompleteDropdown.props.loading).to.equal(false); + expect(searchApi).to.have.been.calledWith("ang", selectedList.map(item => item.id)); expect(autocompleteDropdown.props.items.length).to.equal(2); + expect(autocompleteDropdown.props.items[0].name).to.equal("anguilla"); + expect(autocompleteDropdown.props.items[0].id).to.equal(7); + expect(selectedList.length).to.equal(1); + autocompleteDropdown.props.onChange({index: 0}); - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); - expect(selectedList2.length).to.equal(2); - expect(selectedList2[1].name).to.equal("anguilla"); + + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(2); + expect(selectedList[0].name).to.equal("angola"); + expect(selectedList[0].id).to.equal(6); + expect(selectedList[1].name).to.equal("anguilla"); + expect(selectedList[1].id).to.equal(7); expect(autocompleteDropdown.props.items.length).to.equal(1); + expect(autocompleteDropdown.props.items[0].name).to.equal("bangladesh"); + expect(autocompleteDropdown.props.items[0].id).to.equal(18); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); - expect(selectedList2.length).to.equal(1); - expect(searchApi).to.have.been.calledWith("", selectedList2.map(item => item.id)); + + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(1); + expect(selectedList[0].name).to.equal("angola"); + expect(selectedList[0].id).to.equal(6); + autocompleteInput.value = "ang"; TestUtils.Simulate.change(autocompleteInput); + expect(autocompleteDropdown.props.opened).to.equal(true); expect(autocompleteDropdown.props.loading).to.equal(true); + return timeout(function() { - expect(searchApi).to.have.been.calledWith("ang", selectedList2.map(item => item.id)); + expect(autocompleteDropdown.props.loading).to.equal(false); + expect(searchApi).to.have.been.calledWith("ang", selectedList.map(item => item.id)); expect(autocompleteDropdown.props.items.length).to.equal(2); expect(autocompleteDropdown.props.items[0].name).to.equal("anguilla"); + expect(autocompleteDropdown.props.items[0].id).to.equal(7); + expect(selectedList.length).to.equal(1); }, 360); },360); }, 360); }); + it("should delete item if click is pressed", function() { + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(0); + + autocompleteInput.value = "ang"; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + + return timeout(function () { + expect(autocompleteDropdown.props.loading).to.equal(false); + expect(searchApi).to.have.been.calledWith("ang", selectedList.map(item => item.id)); + expect(autocompleteDropdown.props.items.length).to.equal(3); + expect(selectedList.length).to.equal(0); + + autocompleteDropdown.props.onChange({index: 0}); + + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(searchApi).to.have.been.calledWith("", selectedList.map(item => item.id)); + expect(selectedList.length).to.equal(1); + expect(selectedList[0].name).to.equal("angola"); + expect(selectedList[0].id).to.equal(6); + + tag = TestUtils.scryRenderedComponentsWithType(autocompleteWithExternalApi, Tag)[0]; + tag.props.onRemoveClick({ preventDefault: stub() }); + + expect(selectedList.length).to.equal(0); + + autocompleteInput.value = "ang"; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.loading).to.equal(true); + + return timeout(function () { + expect(autocompleteDropdown.props.loading).to.equal(false); + expect(searchApi).to.have.been.calledWith("ang", selectedList.map(item => item.id)); + expect(autocompleteDropdown.props.items.length).to.equal(3); + expect(autocompleteDropdown.props.items[0].name).to.equal("angola"); + expect(autocompleteDropdown.props.items[0].id).to.equal(6); + expect(selectedList.length).to.equal(0); + }, 360); + }, 360); + }); + }); + + describe('Autocomplete component with items', function() { + let selectedList = [], autocompleteInput, autocompleteDropdown, itemsList, autocomplete; + function renderAutocomplete(props) { + selectedList = []; + itemsList = [ + {id: 45, name: 'lautaro', content: 'Lautaro.', color: 'gray'}, + {id: 46, name: 'dsafa', content: 'dsafa', color: 'black'}, + {id: 47, name: 'asdasdasd', content: 'asdasdasd.', color: 'red'}, + {id: 48, name: '123123123', content: '123123123.', color: 'blue'}, + {id: 49, name: 'hola', content: 'hola', color: 'green'}, + ]; + + autocomplete = TestUtils.renderIntoDocument( + selectedList = selectedListAutocomplete} /> + ); + + autocompleteInput = TestUtils.scryRenderedDOMComponentsWithClass(autocomplete, 'autocomplete__input')[0]; + autocompleteDropdown = TestUtils.scryRenderedComponentsWithType(autocomplete, DropDown)[0]; + } + + beforeEach(function() { + renderAutocomplete(); + }); + + it('should open menu with list', function() { + expect(selectedList.length).to.equal(0); + + autocompleteInput.value = "la"; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(2); + }); + + it('should select item if enter is pressed', function() { + expect(selectedList.length).to.equal(0); + + autocompleteInput.value = "la"; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(2); + expect(autocompleteDropdown.props.items[0].name).to.equal("lautaro"); + expect(autocompleteDropdown.props.items[0].id).to.equal(45); + expect(autocompleteDropdown.props.items[1].name).to.equal("hola"); + expect(autocompleteDropdown.props.items[1].id).to.equal(49); + + autocompleteDropdown.props.onChange({index: 0}); + + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(selectedList.length).to.equal(1); + expect(selectedList[0].name).to.equal("lautaro"); + expect(selectedList[0].id).to.equal(45); + + autocompleteInput.value = ""; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(4); + expect(autocompleteDropdown.props.items[0].name).to.equal("dsafa"); + expect(autocompleteDropdown.props.items[0].id).to.equal(46); + expect(autocompleteDropdown.props.items[1].name).to.equal("asdasdasd"); + expect(autocompleteDropdown.props.items[1].id).to.equal(47); + expect(autocompleteDropdown.props.items[2].name).to.equal("123123123"); + expect(autocompleteDropdown.props.items[2].id).to.equal(48); + expect(autocompleteDropdown.props.items[3].name).to.equal("hola"); + expect(autocompleteDropdown.props.items[3].id).to.equal(49); + + autocompleteDropdown.props.onChange({index: 2}); + + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(selectedList.length).to.equal(2); + expect(selectedList[0].name).to.equal("lautaro"); + expect(selectedList[0].id).to.equal(45); + expect(selectedList[1].name).to.equal("123123123"); + expect(selectedList[1].id).to.equal(48); + + autocompleteInput.value = ""; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(3); + expect(autocompleteDropdown.props.items[0].name).to.equal("dsafa"); + expect(autocompleteDropdown.props.items[0].id).to.equal(46); + expect(autocompleteDropdown.props.items[1].name).to.equal("asdasdasd"); + expect(autocompleteDropdown.props.items[1].id).to.equal(47); + expect(autocompleteDropdown.props.items[2].name).to.equal("hola"); + expect(autocompleteDropdown.props.items[2].id).to.equal(49); + + autocompleteInput.value = "lau"; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(0); + + autocompleteInput.value = "la"; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(1); + expect(autocompleteDropdown.props.items[0].name).to.equal("hola"); + expect(autocompleteDropdown.props.items[0].id).to.equal(49); + + autocompleteDropdown.props.onChange({index: 0}); + + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(selectedList.length).to.equal(3); + expect(selectedList[0].name).to.equal("lautaro"); + expect(selectedList[0].id).to.equal(45); + expect(selectedList[1].name).to.equal("123123123"); + expect(selectedList[1].id).to.equal(48); + expect(selectedList[2].name).to.equal("hola"); + expect(selectedList[2].id).to.equal(49); + }); + + it('should delete item if (backspace or click) is pressed and input value is "" ', function() { + expect(selectedList.length).to.equal(0); + + autocompleteInput.value = "123"; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(1); + expect(autocompleteDropdown.props.items[0].name).to.equal("123123123"); + expect(autocompleteDropdown.props.items[0].id).to.equal(48); + + autocompleteDropdown.props.onChange({index: 0}); + + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(selectedList.length).to.equal(1); + expect(selectedList[0].name).to.equal("123123123"); + expect(selectedList[0].id).to.equal(48); + + autocompleteInput.value = "la"; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(2); + expect(autocompleteDropdown.props.items[0].name).to.equal("lautaro"); + expect(autocompleteDropdown.props.items[0].id).to.equal(45); + expect(autocompleteDropdown.props.items[1].name).to.equal("hola"); + expect(autocompleteDropdown.props.items[1].id).to.equal(49); + + autocompleteDropdown.props.onChange({index: 1}); + + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(selectedList.length).to.equal(2); + expect(selectedList[0].name).to.equal("123123123"); + expect(selectedList[0].id).to.equal(48); + expect(selectedList[1].name).to.equal("hola"); + expect(selectedList[1].id).to.equal(49); + + autocompleteInput.value = "l"; + TestUtils.Simulate.change(autocompleteInput); + + expect(autocompleteDropdown.props.opened).to.equal(true); + expect(autocompleteDropdown.props.items.length).to.equal(1); + expect(autocompleteDropdown.props.items[0].name).to.equal("lautaro"); + expect(autocompleteDropdown.props.items[0].id).to.equal(45); + + autocompleteDropdown.props.onChange({index: 0}); + + expect(autocompleteDropdown.props.opened).to.equal(false); + expect(selectedList.length).to.equal(3); + expect(selectedList[0].name).to.equal("123123123"); + expect(selectedList[0].id).to.equal(48); + expect(selectedList[1].name).to.equal("hola"); + expect(selectedList[1].id).to.equal(49); + expect(selectedList[2].name).to.equal("lautaro"); + expect(selectedList[2].id).to.equal(45); + + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + + expect(selectedList.length).to.equal(2); + + autocompleteInput.value = "asd"; + TestUtils.Simulate.change(autocompleteInput); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + + expect(selectedList.length).to.equal(2); + expect(selectedList[0].name).to.equal("123123123"); + expect(selectedList[0].id).to.equal(48); + expect(selectedList[1].name).to.equal("hola"); + expect(selectedList[1].id).to.equal(49); + + tag = TestUtils.scryRenderedComponentsWithType(autocomplete, Tag)[0]; + tag.props.onRemoveClick({ preventDefault: stub() }); + + expect(selectedList.length).to.equal(1); + expect(selectedList[0].name).to.equal("hola"); + expect(selectedList[0].id).to.equal(49); + + autocompleteInput.value = "a"; + TestUtils.Simulate.change(autocompleteInput); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + + expect(selectedList.length).to.equal(1); + + autocompleteInput.value = ""; + TestUtils.Simulate.change(autocompleteInput); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + + expect(selectedList.length).to.equal(0); + + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + TestUtils.Simulate.keyDown(autocompleteInput, {key: 'backspace', keyCode: 8, which: 8}); + + expect(selectedList.length).to.equal(0); + }); }); }); diff --git a/client/src/core-components/autocomplete-dropdown.js b/client/src/core-components/autocomplete-dropdown.js deleted file mode 100644 index ce6e1832..00000000 --- a/client/src/core-components/autocomplete-dropdown.js +++ /dev/null @@ -1,267 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import keyCode from 'keycode'; - -import DropDown from 'core-components/drop-down'; -import Tag from 'core-components/tag'; - -const ItemsSchema = React.PropTypes.arrayOf(React.PropTypes.shape({ - id: React.PropTypes.number, - name: React.PropTypes.string, - content: React.PropTypes.string, - color: React.PropTypes.string, -})); - -class AutocompleteDropDown extends React.Component { - - static propTypes = { - items: ItemsSchema, - onChange: React.PropTypes.func, - values: ItemsSchema, - onRemoveClick: React.PropTypes.func, - onTagSelected: React.PropTypes.func, - getItemListFromQuery: React.PropTypes.func, - disabled: React.PropTypes.bool, - }; - - id = 1; - - state = { - selectedItems: [], - inputValue: "", - opened: false, - highlightedIndex: 0, - itemsFromQuery: [], - loading: false, - }; - - componentDidMount() { - const { getItemListFromQuery, } = this.props; - - this.setTimeout = _.throttle((query) => { - let id = ++this.id; - - getItemListFromQuery(query, this.getSelectedItems().map(item => item.id)) - .then(result => { - if(id === this.id) - this.setState({ - itemsFromQuery: result, - loading: false, - }); - }) - .catch(() => this.setState({ - loading: false, - })); - }, 300, {leading: false}); - - this.searchApi(""); - } - - render() { - let inputWidth = 0; - - if(this.span) { - this.span.style.display = 'inline'; - this.span.textContent = this.state.inputValue; - inputWidth = Math.ceil(this.span.getBoundingClientRect().width)-31; - this.span.style.display = 'none'; - } - - return ( -
- -
- ); - } - - renderSelectedItems() { - return this.getSelectedItems().map(item => this.renderSelectedItem(item)); - } - - renderSelectedItem(item) { - return - } - - getDropdownList() { - const { - items, - } = this.props; - let dropdownList = []; - - if(items !== undefined) { - const list = this.getUnselectedList(items, this.getSelectedItems()); - - dropdownList = list.filter(s => _.includes(s.name, this.state.inputValue)); - } else { - dropdownList = this.getUnselectedList(this.state.itemsFromQuery, this.getSelectedItems()); - } - - return dropdownList; - } - - getUnselectedList(list, selectedList) { - return list.filter(item => !_.some(selectedList, item)); - } - - getSelectedItems() { - const { values, } = this.props; - - return (values !== undefined) ? values : this.state.selectedItems; - } - - onRemoveClick(itemId, event) { - const { - onChange, - onRemoveClick, - } = this.props; - const newList = this.getSelectedItems().filter(item => item.id != itemId); - event.preventDefault(); - - this.setState({ - selectedItems: newList, - opened: false, - highlightedIndex: 0, - }); - - onChange && onChange(newList); - onRemoveClick && onRemoveClick(itemId); - this.searchApi("", newList); - } - - onChangeDropDown(e) { - const { - onChange, - onTagSelected, - } = this.props; - - if(this.getDropdownList().length) { - const itemSelected = this.getDropdownList()[e.index]; - const newList = [...this.getSelectedItems(), itemSelected]; - - this.setState({ - selectedItems: newList, - inputValue: "", - highlightedIndex: 0, - }); - - onChange && onChange(newList); - onTagSelected && onTagSelected(itemSelected.id); - this.searchApi("", newList); - } - } - - onChangeInput(str) { - const { getItemListFromQuery, } = this.props; - - this.setState({ - inputValue: str, - opened: true, - highlightedIndex: 0, - }); - - if(getItemListFromQuery !== undefined) { - this.setState({ - loading: true, - }); - - this.setTimeout(str); - } - } - - onMenuToggle(b) { - this.setState({ - opened: b, - }); - } - - onHighlightedIndexChange(n) { - this.setState({ - highlightedIndex: n, - }); - } - - onKeyDown(event) { - const { - onChange, - onRemoveClick, - } = this.props; - - if(this.props.disabled) { - event.stopPropagation(); - event.preventDefault(); - - return; - } - - if(keyCode(event) === "space") { - event.stopPropagation(); - } - - if(keyCode(event) === "backspace" && this.state.inputValue === "") { - const lastSelectedItemsIndex = this.getSelectedItems().length-1; - const newList = this.getSelectedItems().slice(0, lastSelectedItemsIndex); - this.setState({ - selectedItems: newList, - highlightedIndex: 0, - }); - onChange && onChange(newList); - - if(this.getSelectedItems().length) { - const itemId = this.getSelectedItems()[lastSelectedItemsIndex].id; - - onRemoveClick && onRemoveClick(itemId); - } - - this.searchApi("", newList); - } - } - - searchApi(query, blacklist=this.getSelectedItems()) { - const { getItemListFromQuery, } = this.props; - - if(getItemListFromQuery !== undefined) { - getItemListFromQuery(query, blacklist.map(item => item.id)) - .then(result => { - this.setState({ - itemsFromQuery: result, - loading: false, - }); - }) - .catch(() => { - this.setState({ - loading: false, - }); - }); - } - } -} - -export default AutocompleteDropDown; diff --git a/client/src/core-components/autocomplete-dropdown.scss b/client/src/core-components/autocomplete-dropdown.scss deleted file mode 100644 index 46a9af7e..00000000 --- a/client/src/core-components/autocomplete-dropdown.scss +++ /dev/null @@ -1,42 +0,0 @@ -@import "../scss/vars"; - -.autocomplete { - margin-bottom: 30px; - text-align: left; - - &__drop-down { - .drop-down__current-item { - cursor: text; - background-color: $very-light-grey; - border: 1px solid $grey; - min-height: 38px; - - &:focus { - outline: none; - border-color: $primary-blue; - } - } - } - - &__label { - display: inline-block; - } - - &__input { - display: inline-block; - border: 0; - background: transparent; - outline: none; - padding-left: 5px; - width: 10px; - max-width: 100%; - min-width: 10px; - } - - .sizer { - line-height: 1.21428571em; - padding: .67857143em 2.1em .67857143em 1em; - display: none; - white-space: pre; - } -} From 71a1d434ef5fffffd6abdd57099aaf535975f552 Mon Sep 17 00:00:00 2001 From: Ivan Diaz Date: Wed, 22 Jan 2020 14:26:07 -0300 Subject: [PATCH 14/16] Add base schema population --- server/composer.json | 2 +- server/controllers/system/init-settings.php | 2 + server/data/db_schema.sql | 473 ++++++++++++++++++++ server/index.php | 2 + server/models/DataStore.php | 18 +- server/models/Ticket.php | 8 + server/models/Ticketevent.php | 7 + tests/init.rb | 1 + 8 files changed, 510 insertions(+), 3 deletions(-) create mode 100644 server/data/db_schema.sql diff --git a/server/composer.json b/server/composer.json index 49882297..698d7607 100755 --- a/server/composer.json +++ b/server/composer.json @@ -4,7 +4,7 @@ "respect/validation": "^1.1", "phpmailer/phpmailer": "^5.2", "google/recaptcha": "~1.1", - "gabordemooij/redbean": "^4.3", + "gabordemooij/redbean": "^5.4", "ifsnop/mysqldump-php": "2.*", "ezyang/htmlpurifier": "^4.8", "codeguy/upload": "^1.3", diff --git a/server/controllers/system/init-settings.php b/server/controllers/system/init-settings.php index 083a40cf..90d6768d 100755 --- a/server/controllers/system/init-settings.php +++ b/server/controllers/system/init-settings.php @@ -1,4 +1,5 @@ storeGlobalSettings(); $this->storeMailTemplates(); $this->storeLanguages(); diff --git a/server/data/db_schema.sql b/server/data/db_schema.sql new file mode 100644 index 00000000..eee1ce58 --- /dev/null +++ b/server/data/db_schema.sql @@ -0,0 +1,473 @@ +-- MySQL dump 10.13 Distrib 5.6.46, for Linux (x86_64) +-- ------------------------------------------------------ +-- Server version 5.6.46 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `apikey` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `apikey` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `token` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `article` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `article` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `content` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `last_edited` double DEFAULT NULL, + `position` tinyint(1) unsigned DEFAULT NULL, + `topic_id` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `index_foreignkey_article_topic` (`topic_id`), + CONSTRAINT `c_fk_article_topic_id` FOREIGN KEY (`topic_id`) REFERENCES `topic` (`id`) ON DELETE SET NULL ON UPDATE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `ban` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `ban` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `email` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `customfield` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `customfield` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `description` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `customfieldoption` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `customfieldoption` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `customfield_id` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `index_foreignkey_customfieldoption_customfield` (`customfield_id`), + CONSTRAINT `c_fk_customfieldoption_customfield_id` FOREIGN KEY (`customfield_id`) REFERENCES `customfield` (`id`) ON DELETE SET NULL ON UPDATE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `customfieldvalue` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `customfieldvalue` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `value` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `user_id` int(11) unsigned DEFAULT NULL, + `customfield_id` int(11) unsigned DEFAULT NULL, + `customfieldoption_id` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `index_foreignkey_customfieldvalue_user` (`user_id`), + KEY `index_foreignkey_customfieldvalue_customfield` (`customfield_id`), + KEY `index_foreignkey_customfieldvalue_customfieldoption` (`customfieldoption_id`), + CONSTRAINT `c_fk_customfieldvalue_customfield_id` FOREIGN KEY (`customfield_id`) REFERENCES `customfield` (`id`) ON DELETE SET NULL ON UPDATE SET NULL, + CONSTRAINT `c_fk_customfieldvalue_customfieldoption_id` FOREIGN KEY (`customfieldoption_id`) REFERENCES `customfieldoption` (`id`) ON DELETE SET NULL ON UPDATE SET NULL, + CONSTRAINT `c_fk_customfieldvalue_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `customresponse` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `customresponse` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `language` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `content` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `department` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `department` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `owners` int(11) unsigned DEFAULT NULL, + `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `private` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `department_staff` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `department_staff` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `department_id` int(11) unsigned DEFAULT NULL, + `staff_id` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQ_f2a19cacc73eb380315c104650ebc8c42d1b75a0` (`department_id`,`staff_id`), + KEY `index_foreignkey_department_staff_department` (`department_id`), + KEY `index_foreignkey_department_staff_staff` (`staff_id`), + CONSTRAINT `c_fk_department_staff_department_id` FOREIGN KEY (`department_id`) REFERENCES `department` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `c_fk_department_staff_staff_id` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `language` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `language` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `code` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `allowed` int(11) unsigned DEFAULT NULL, + `supported` tinyint(1) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `log` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `log` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `to` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `date` double DEFAULT NULL, + `author_name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `author_staff_id` int(11) unsigned DEFAULT NULL, + `author_user_id` int(11) unsigned DEFAULT NULL, + `author_staff` tinyint(1) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `index_foreignkey_log_author_staff` (`author_staff_id`), + KEY `index_foreignkey_log_author_user` (`author_user_id`), + CONSTRAINT `c_fk_log_author_staff_id` FOREIGN KEY (`author_staff_id`) REFERENCES `staff` (`id`) ON DELETE SET NULL ON UPDATE SET NULL, + CONSTRAINT `c_fk_log_author_user_id` FOREIGN KEY (`author_user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL ON UPDATE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `mailtemplate` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `mailtemplate` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `template` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `subject` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `language` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `text1` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `text2` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `text3` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `recoverpassword` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `recoverpassword` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `email` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `token` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `staff` tinyint(1) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `sessioncookie` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `sessioncookie` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `token` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `ip` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `creation_date` double DEFAULT NULL, + `expiration_date` double DEFAULT NULL, + `user_id` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `index_foreignkey_sessioncookie_user` (`user_id`), + CONSTRAINT `c_fk_sessioncookie_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL ON UPDATE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `setting` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `setting` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `value` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `staff` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `staff` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `level` int(11) unsigned DEFAULT NULL, + `send_email_on_new_ticket` int(11) unsigned DEFAULT NULL, + `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `email` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `password` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `profile_pic` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `verification_token` tinyint(1) unsigned DEFAULT NULL, + `disabled` tinyint(1) unsigned DEFAULT NULL, + `last_login` double DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `staff_ticket` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `staff_ticket` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `ticket_id` int(11) unsigned DEFAULT NULL, + `staff_id` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQ_8a53d6ad25611e7060ab030785c3c0cef48bc533` (`staff_id`,`ticket_id`), + KEY `index_foreignkey_staff_ticket_ticket` (`ticket_id`), + KEY `index_foreignkey_staff_ticket_staff` (`staff_id`), + CONSTRAINT `c_fk_staff_ticket_staff_id` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `c_fk_staff_ticket_ticket_id` FOREIGN KEY (`ticket_id`) REFERENCES `ticket` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `tag` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `tag` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `color` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `tag_ticket` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `tag_ticket` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `tag_id` int(11) unsigned DEFAULT NULL, + `ticket_id` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQ_79dc4901a5620abff6d65219e6aef86fe9ab63bc` (`tag_id`,`ticket_id`), + KEY `index_foreignkey_tag_ticket_tag` (`tag_id`), + KEY `index_foreignkey_tag_ticket_ticket` (`ticket_id`), + CONSTRAINT `c_fk_tag_ticket_tag_id` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `c_fk_tag_ticket_ticket_id` FOREIGN KEY (`ticket_id`) REFERENCES `ticket` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `ticket` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `ticket` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `ticket_number` int(11) unsigned DEFAULT NULL, + `unread` tinyint(1) unsigned DEFAULT NULL, + `priority` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `unread_staff` tinyint(1) unsigned DEFAULT NULL, + `title` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `content` text COLLATE utf8mb4_unicode_ci, + `language` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `file` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `date` double DEFAULT NULL, + `closed` tinyint(1) unsigned DEFAULT NULL, + `author_email` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `author_name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `department_id` int(11) unsigned DEFAULT NULL, + `author` tinyint(1) unsigned DEFAULT NULL, + `author_id` int(11) unsigned DEFAULT NULL, + `author_staff` tinyint(1) unsigned DEFAULT NULL, + `author_staff_id` int(11) unsigned DEFAULT NULL, + `owner_id` int(11) unsigned DEFAULT NULL, + `owner` tinyint(1) unsigned DEFAULT NULL, + `edited_content` tinyint(1) unsigned DEFAULT NULL, + `edited_title` tinyint(1) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `index_foreignkey_ticket_department` (`department_id`), + KEY `index_foreignkey_ticket_author` (`author_id`), + KEY `index_foreignkey_ticket_author_staff` (`author_staff_id`), + KEY `index_foreignkey_ticket_owner` (`owner_id`), + CONSTRAINT `c_fk_ticket_author_id` FOREIGN KEY (`author_id`) REFERENCES `user` (`id`) ON DELETE SET NULL ON UPDATE SET NULL, + CONSTRAINT `c_fk_ticket_author_staff_id` FOREIGN KEY (`author_staff_id`) REFERENCES `staff` (`id`) ON DELETE SET NULL ON UPDATE SET NULL, + CONSTRAINT `c_fk_ticket_department_id` FOREIGN KEY (`department_id`) REFERENCES `department` (`id`) ON DELETE SET NULL ON UPDATE SET NULL, + CONSTRAINT `c_fk_ticket_owner_id` FOREIGN KEY (`owner_id`) REFERENCES `staff` (`id`) ON DELETE SET NULL ON UPDATE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `ticket_user` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `ticket_user` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `ticket_id` int(11) unsigned DEFAULT NULL, + `user_id` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `UQ_f73b0a7296c1f9101e14c7c4bd6476b9f9a6a2b7` (`ticket_id`,`user_id`), + KEY `index_foreignkey_ticket_user_ticket` (`ticket_id`), + KEY `index_foreignkey_ticket_user_user` (`user_id`), + CONSTRAINT `c_fk_ticket_user_ticket_id` FOREIGN KEY (`ticket_id`) REFERENCES `ticket` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `c_fk_ticket_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `ticketevent` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `ticketevent` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `type` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `content` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `date` double DEFAULT NULL, + `ticket_id` int(11) unsigned DEFAULT NULL, + `author_staff_id` int(11) unsigned DEFAULT NULL, + `file` tinyint(1) unsigned DEFAULT NULL, + `private` int(11) unsigned DEFAULT NULL, + `author_user_id` int(11) unsigned DEFAULT NULL, + `edited_content` tinyint(1) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `index_foreignkey_ticketevent_ticket` (`ticket_id`), + KEY `index_foreignkey_ticketevent_author_staff` (`author_staff_id`), + KEY `index_foreignkey_ticketevent_author_user` (`author_user_id`), + CONSTRAINT `c_fk_ticketevent_author_staff_id` FOREIGN KEY (`author_staff_id`) REFERENCES `staff` (`id`) ON DELETE SET NULL ON UPDATE SET NULL, + CONSTRAINT `c_fk_ticketevent_author_user_id` FOREIGN KEY (`author_user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL ON UPDATE SET NULL, + CONSTRAINT `c_fk_ticketevent_ticket_id` FOREIGN KEY (`ticket_id`) REFERENCES `ticket` (`id`) ON DELETE SET NULL ON UPDATE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `topic` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `topic` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `icon` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `icon_color` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `private` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `user` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `user` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `email` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `password` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `signup_date` double DEFAULT NULL, + `tickets` int(11) unsigned DEFAULT NULL, + `verification_token` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `disabled` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2020-01-18 20:58:21 diff --git a/server/index.php b/server/index.php index 9f038796..c9531bb8 100644 --- a/server/index.php +++ b/server/index.php @@ -9,6 +9,8 @@ if(defined('MYSQL_HOST') && defined('MYSQL_DATABASE') && defined('MYSQL_USER') & if(!defined('MYSQL_PORT')) define('MYSQL_PORT', '3306'); RedBean::setup('mysql:host='. MYSQL_HOST . ';port=' . MYSQL_PORT . ';dbname=' . MYSQL_DATABASE , MYSQL_USER, MYSQL_PASSWORD); RedBean::setAutoResolve(true); + // TODO: Implement freeze + // RedBean::freeze(); } // SLIM FRAMEWORK diff --git a/server/models/DataStore.php b/server/models/DataStore.php index 8d458398..888a3ca4 100755 --- a/server/models/DataStore.php +++ b/server/models/DataStore.php @@ -6,7 +6,15 @@ abstract class DataStore { protected $properties = []; public static function isTableEmpty() { - return (RedBean::count(static::TABLE) === 0); + try { + return (RedBean::count(static::TABLE) === 0); + } catch(\Exception $e) { + return true; + } + } + + public static function getFetchAs() { + return []; } public static function getDataStore($value, $property = 'id') { @@ -112,7 +120,13 @@ abstract class DataStore { } private function parseBeanProp($prop) { - $parsedProp = $this->_bean[$prop]; + $fetchAs = static::getFetchAs(); + + if(array_key_exists($prop, $fetchAs)) { + $parsedProp = $this->_bean->fetchAs($fetchAs[$prop])[$prop]; + } else { + $parsedProp = $this->_bean[$prop]; + } if (strpos($prop, 'List')) { $parsedProp = DataStoreList::getList($this->getListType($prop), $parsedProp); diff --git a/server/models/Ticket.php b/server/models/Ticket.php index 991487fc..a609b648 100755 --- a/server/models/Ticket.php +++ b/server/models/Ticket.php @@ -56,6 +56,14 @@ class Ticket extends DataStore { ); } + public static function getFetchAs() { + return [ + 'author' => 'user', + 'authorStaff' => 'staff', + 'owner' => 'staff', + ]; + } + public static function getTicket($value, $property = 'id') { return parent::getDataStore($value, $property); } diff --git a/server/models/Ticketevent.php b/server/models/Ticketevent.php index a8ea9b96..c7018686 100755 --- a/server/models/Ticketevent.php +++ b/server/models/Ticketevent.php @@ -65,6 +65,13 @@ class Ticketevent extends DataStore { ]; } + public static function getFetchAs() { + return [ + 'authorUser' => 'user', + 'authorStaff' => 'staff' + ]; + } + public function getAuthor() { if($this->authorUser) { return $this->authorUser; diff --git a/tests/init.rb b/tests/init.rb index c4291800..bea5c98b 100644 --- a/tests/init.rb +++ b/tests/init.rb @@ -71,6 +71,7 @@ require './ticket/add-tag.rb' require './ticket/delete-tag.rb' require './ticket/edit-comment.rb' require './ticket/edit-title.rb' +require './system/custom-fields.rb' require './system/disable-user-system.rb' require './ticket/search.rb' # require './system/get-stats.rb' From ae4aa4ead96bbc2c9b2d3699c9adde3c937c6cb9 Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Wed, 22 Jan 2020 22:07:39 -0300 Subject: [PATCH 15/16] Autocomplete component Finished --- client/src/core-components/autocomplete.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/core-components/autocomplete.js b/client/src/core-components/autocomplete.js index 1903a13a..3a4fd9d8 100644 --- a/client/src/core-components/autocomplete.js +++ b/client/src/core-components/autocomplete.js @@ -8,7 +8,7 @@ import Tag from 'core-components/tag'; const ItemsSchema = React.PropTypes.arrayOf(React.PropTypes.shape({ id: React.PropTypes.number, name: React.PropTypes.string, - content: React.PropTypes.string, + content: React.PropTypes.node, color: React.PropTypes.string, })); @@ -90,7 +90,7 @@ class Autocomplete extends React.Component { onKeyDown={e => this.onKeyDown(e)} onChange={e => this.onChangeInput(e.target.value)} style={this.span ? {width: inputWidth} : {}} /> - this.span = span} /> + this.span = span} />
@@ -128,6 +128,7 @@ class Autocomplete extends React.Component { } getUnselectedList(list, selectedList) { + return list.filter(item => !_.some(selectedList, item)); } @@ -203,9 +204,9 @@ class Autocomplete extends React.Component { }); } - onHighlightedIndexChange(n) { + onHighlightedIndexChange(index) { this.setState({ - highlightedIndex: n, + highlightedIndex: index, }); } From a6c3cfeba96ca9ddb0fd2a3ab159d7559efea4f2 Mon Sep 17 00:00:00 2001 From: LautaroCesso Date: Wed, 22 Jan 2020 22:14:27 -0300 Subject: [PATCH 16/16] Restore demo-page --- client/src/app/demo/components-demo-page.js | 328 ++++++++++++++++---- 1 file changed, 260 insertions(+), 68 deletions(-) diff --git a/client/src/app/demo/components-demo-page.js b/client/src/app/demo/components-demo-page.js index 06d98c54..b1a21b95 100644 --- a/client/src/app/demo/components-demo-page.js +++ b/client/src/app/demo/components-demo-page.js @@ -1,76 +1,268 @@ -import Autocomplete from 'core-components/autocomplete'; +'use strict'; const React = require('react'); +// const LineChart = require("react-chartjs-2").Line; const _ = require('lodash'); +const DocumentTitle = require('react-document-title'); -const color = [ - 'red', - 'cyan', - 'blue', - 'green', -]; +const ModalContainer = require('app-components/modal-container'); +const AreYouSure = require('app-components/are-you-sure'); -let countries = ["Afghanistan","Åland Islands","Albania","Algeria","American Samoa","AndorrA","Angola","Anguilla","Antarctica","Antigua and Barbuda","Argentina","Armenia","Aruba","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia and Herzegovina","Botswana","Bouvet Island","Brazil","British Indian Ocean Territory","Brunei Darussalam","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Cape Verde","Cayman Islands","Central African Republic","Chad","Chile","China","Christmas Island","Cocos (Keeling) Islands","Colombia","Comoros","Congo","Congo, The Democratic Republic of the","Cook Islands","Costa Rica","Cote D'Ivoire","Croatia","Cuba","Cyprus","Czech Republic","Denmark","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Falkland Islands (Malvinas)","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Heard Island and Mcdonald Islands","Holy See (Vatican City State)","Honduras","Hong Kong","Hungary","Iceland","India","Indonesia","Iran, Islamic Republic Of","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Korea, Democratic People'S Republic of","Korea, Republic of","Kuwait","Kyrgyzstan","Lao People'S Democratic Republic","Latvia","Lebanon","Lesotho","Liberia","Libyan Arab Jamahiriya","Liechtenstein","Lithuania","Luxembourg","Macao","Macedonia, The Former Yugoslav Republic of","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia, Federated States of","Moldova, Republic of","Monaco","Mongolia","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","Netherlands Antilles","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territory, Occupied","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn","Poland","Portugal","Puerto Rico","Qatar","Reunion","Romania","Russian Federation","RWANDA","Saint Helena","Saint Kitts and Nevis","Saint Lucia","Saint Pierre and Miquelon","Saint Vincent and the Grenadines","Samoa","San Marino","Sao Tome and Principe","Saudi Arabia","Senegal","Serbia and Montenegro","Seychelles","Sierra Leone","Singapore","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia and the South Sandwich Islands","Spain","Sri Lanka","Sudan","Suriname","Svalbard and Jan Mayen","Swaziland","Sweden","Switzerland","Syrian Arab Republic","Taiwan, Province of China","Tajikistan","Tanzania, United Republic of","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad and Tobago","Tunisia","Turkey","Turkmenistan","Turks and Caicos Islands","Tuvalu","Uganda","Ukraine","United Arab Emirates","United Kingdom","United States","United States Minor Outlying Islands","Uruguay","Uzbekistan","Vanuatu","Venezuela","Viet Nam","Virgin Islands, British","Virgin Islands, U.S.","Wallis and Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"]; -countries = countries.map((name, index) => { - return { - id: index, - name: name.toLowerCase(), - content: name, - color: color[_.random(0, color.length-1)], +const Button = require('core-components/button'); +const Input = require('core-components/input'); +const Checkbox = require('core-components/checkbox'); +const Widget = require('core-components/widget'); +const DropDown = require('core-components/drop-down'); +const Menu = require('core-components/menu'); +const Tooltip = require('core-components/tooltip'); +const Table = require('core-components/table'); +const InfoTooltip = require('core-components/info-tooltip'); +const TagSelector = require('core-components/tag-selector'); + +function rand(min, max, num) { + var rtn = []; + while (rtn.length < num) { + rtn.push((Math.random() * (max - min)) + min); } -}); - -const searchApi = (query, blacklist = []) => { - const data = countries.filter(x => !_.includes(blacklist, x.id)); - - return new Promise((res,rej) => { - setTimeout(function () { - const result = data.filter(item => _.includes(item.name, query)); - res(result.slice(0, 10)); - }, query == 'brazilq' ? 100 : 50); - }); -}; - -class DemoPage extends React.Component { - - state = { - selectedList: [ - {id: 47, name: 'asdasdasd', content: 'asdasdasd.', color: 'red'}, - {id: 48, name: '123123123', content: '123123123.', color: 'blue'}, - {id: 49, name: 'hola', content: 'hola', color: 'green'}, - ], - selectedList2: [], - } - - render() { - let itemsList = [ - {id: 45, name: 'lautaro', content: 'Lautaro.', color: 'gray'}, - {id: 46, name: 'dsafa', content: 'dsafa', color: 'black'}, - {id: 47, name: 'asdasdasd', content: 'asdasdasd.', color: 'red'}, - {id: 48, name: '123123123', content: '123123123.', color: 'blue'}, - {id: 49, name: 'hola', content: 'hola', color: 'green'}, - ]; - - return ( -
- this.setState({selectedList: selectedList})} /> - - this.setState({selectedList2: selectedList})} /> - -
- ); - } + return rtn; } -export default DemoPage; +let chartData = { + labels: ["January", "February", "March", "April", "May", "June"], + datasets: [ + { + label: "My Second dataset", + fill: false, + pointColor: "rgba(151,187,205,1)", + pointStrokeColor: "#fff", + pointHighlightFill: "#fff", + pointHighlightStroke: "rgba(151,187,205,1)", + borderWidth: 3, + data: rand(32, 100, 6), + pointRadius: 0 + }, + { + label: "My Second dataset", + fill: false, + pointColor: "rgba(151,187,205,1)", + pointStrokeColor: "#fff", + pointHighlightFill: "#fff", + pointHighlightStroke: "rgba(151,187,205,1)", + borderWidth: 3, + data: rand(32, 100, 6) + } + ] +}; +let chartOptions = {}; +let dropDownItems = [{content: 'English'}, {content: 'Spanish'}, {content: 'German'}, {content: 'Portuguese'}, {content: 'Japanese'}]; +let secondaryMenuItems = [ + {content: 'My Tickets', icon: 'file-text'}, + {content: 'New Ticket', icon: 'plus'}, + {content: 'Articles', icon: 'book'}, + {content: 'Edit Profile', icon: 'pencil'}, + {content: 'Close Session', icon: 'lock'} +]; + +let DemoPage = React.createClass({ + + propTypes: { + currentUser: React.PropTypes.object.isRequired + }, + + elements: [ + { + title: 'Primary Button', + render: ( + + ) + }, + { + title: 'Tag selector', + render: ( + console.log('deleted click', e)} + onTagSelected={(e) => console.log('selected click', e)} + /> + ) + }, + { + title: 'Input', + render: ( + + ) + }, + { + title: 'Input wrapped in a label', + render: ( + + ) + }, + { + title: 'Checkbox', + render: ( + + ) + }, + { + title: 'Widget', + render: ( + +

Register here!

+ + +
+ ) + }, + { + title: 'Primary Menu', + render: ( + + ) + }, + { + title: 'Secondary Menu', + render: ( + + ) + }, + { + title: 'Navigation Menu', + render: ( + + ) + }, + { + title: 'Horizontal Menu', + render: ( + + ) + }, + { + title: 'HorizontalList Menu', + render: ( + + ) + }, + { + title: 'DropDown', + render: ( + + ) + }, + { + title: 'Tooltip', + render: ( +
+ + hola + +
+ ) + }, + { + title: 'ModalTrigger', + render: ( + + ) + }, + { + title: 'ModalTrigger Large', + render: ( +
+ ); + }}> + Open Large Modal + + ) + }, + { + title: 'Table', + render: ( +
b.title1) + ans = 1; + return ans; + }}/> + ) + }, + { + title: 'InfoTooltip', + render: ( + + ) + }, + { + title: 'LineChart', + // render: ( + // + // ), + render: ( + null + ) + } + ], + + render() { + return ( + +
+ {this.renderElements()} +
+
+ ); + }, + + renderElements: function () { + return this.elements.map((element) => { + return ( +
+

+ {element.title} +

+
+ {element.render} +
+
+ ); + }); + } +}); + +export default DemoPage; \ No newline at end of file