diff --git a/src/App.tsx b/src/App.tsx index 9dc4019..0ae4480 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,7 @@ import { CatRutas } from './Components/Catalogos/CatRutas' import { OpMonitor } from './Components/Operaciones/OpMonitor' import { CatServicios } from './Components/Catalogos/CatServicios' import { CatUsuarios } from './Components/Catalogos/CatUsuarios' +import { ResetAccount } from './Components/ResetAccount/ResetAccount' function App() { const UserLogued = useSelector((state: RootState) => state.UserProfile.UserProfile.logueado) @@ -42,6 +43,7 @@ function App() { }> */} }> }> + }> }> } /> } /> diff --git a/src/Components/Catalogos/CatUsuarios.tsx b/src/Components/Catalogos/CatUsuarios.tsx index b088cf7..bf72e59 100644 --- a/src/Components/Catalogos/CatUsuarios.tsx +++ b/src/Components/Catalogos/CatUsuarios.tsx @@ -1,16 +1,118 @@ -import React, { FC, useState } from 'react' -import { Card, Col, FloatingLabel, Form, Row } from 'react-bootstrap' +import React, { FC, useEffect, useState } from 'react' +import { Button, Card, Col, FloatingLabel, Form, Row } from 'react-bootstrap' import { useSelector } from 'react-redux' import { RootState } from '../../store/store' +import DSCatUsuarios from '../../Services/Catalogos/CatUsuarios.Services' +import ICatUsuarios from '../../Interfaces/Catalogos/ICatUsuarios' +import DataTable from 'react-data-table-component' +import { IconContext } from 'react-icons' +import { BsFillPencilFill } from 'react-icons/bs' +import Clean from '../../images/Clean.png' interface IProps {} export const CatUsuarios: FC = (props) => { const mCatClientes = useSelector((state: RootState) => state.CatClientes.CatClientes) const mCatProveedores = useSelector((state: RootState) => state.CatProveedores.CatProveedores) + const [DataUsuarios, setDataUsuarios] = useState([]) + const [IDUsuario, setIDUsuario] = useState(0) const [Usuario, setUsuario] = useState('') + const [Nombre, setNombre] = useState('') const [TipoUsuario, setTipoUsuario] = useState(0) const [Search, setSearch] = useState('') + const [SearchUser, setSearchUser] = useState('') + const columnsConcepts = [ + { + name: 'id', + width: '80px', + selector: (row: ICatUsuarios) => row.id, + sortable: true, + }, + { + name: 'Nombre', + width: '250px', + selector: (row: ICatUsuarios) => row.nombre, + sortable: true, + }, + { + name: 'Usuario', + width: '250px', + selector: (row: ICatUsuarios) => row.usuario, + sortable: true, + }, + { + name: 'Activo', + width: '90px', + selector: (row: ICatUsuarios) => (row.activo === 1 ? 'Si' : 'No'), + sortable: true, + }, + { + name: 'Editar', + width: '100px', + cell: (row: ICatUsuarios) => ( +
{ + showInfo(row) + }} + > + + + +
+ ), + sortable: true, + }, + ] + + const showInfo = (row: ICatUsuarios) => { + setUsuario(row.usuario) + setNombre(row.nombre) + setTipoUsuario(row.tipoUsuario) + setIDUsuario(row.id) + } + + const saveForm = () => { + const data: ICatUsuarios = { + id: IDUsuario, + usuario: Usuario, + contrasena: '', + nombre: Nombre, + tipoUsuario: TipoUsuario, + idPerfil: 0, + activo: 1, + } + DSCatUsuarios.Append(data) + .then((response) => { + cleanForm() + loadUsers() + }) + .catch((e: Error) => { + alert('Ocurrio un error' + e.message.toString()) + }) + } + + const loadUsers = () => { + DSCatUsuarios.GetAll() + .then((response) => { + console.log(response.data) + setDataUsuarios(response.data) + }) + .catch((e: Error) => { + alert('Ocurrio un error' + e.message.toString()) + }) + } + + const cleanForm = () => { + setUsuario('') + setNombre('') + setTipoUsuario(0) + setIDUsuario(0) + } + + useEffect(() => { + loadUsers() + }, []) return (
@@ -22,12 +124,19 @@ export const CatUsuarios: FC = (props) => { - + - + { + setNombre(e.target.value) + }} + /> - + = (props) => { }} > - - - + + + + - - - - + + Limpia formulario { + cleanForm() + }} + /> - - - + + + { + setUsuario(e.target.value) + }} + /> + + + @@ -78,7 +210,7 @@ export const CatUsuarios: FC = (props) => {
- {TipoUsuario === 2 + {TipoUsuario === 1 ? mCatClientes .filter((c) => c.cliente.toLocaleLowerCase().includes(Search.toLocaleLowerCase())) .map((c) => { @@ -92,7 +224,7 @@ export const CatUsuarios: FC = (props) => { ) }) : null} - {TipoUsuario === 3 + {TipoUsuario === 2 ? mCatProveedores .filter((c) => c.proveedor.toLocaleLowerCase().includes(Search.toLocaleLowerCase())) .map((c) => { @@ -113,6 +245,42 @@ export const CatUsuarios: FC = (props) => { + + + { + setSearchUser(e.target.value) + }} + /> + + + + + + + + +
diff --git a/src/Components/Login/Login.tsx b/src/Components/Login/Login.tsx index 47a60f5..16001d1 100644 --- a/src/Components/Login/Login.tsx +++ b/src/Components/Login/Login.tsx @@ -1,5 +1,5 @@ import { FC, useEffect, useState } from 'react' -import { useNavigate } from 'react-router-dom' +import { Link, Navigate, useNavigate } from 'react-router-dom' import IAuth from '../../Interfaces/Auth/IAuth' import DSAuth from '../../Services/Auth/Auth.Services' import { useDispatch, useSelector } from 'react-redux' @@ -148,6 +148,9 @@ export const Login: FC = (props) => { login
+
+ Olvido contraseña +
diff --git a/src/Components/Operaciones/OpMonitor.tsx b/src/Components/Operaciones/OpMonitor.tsx index 73ff9ab..adb3a18 100644 --- a/src/Components/Operaciones/OpMonitor.tsx +++ b/src/Components/Operaciones/OpMonitor.tsx @@ -6,6 +6,7 @@ import { BsChevronRight, BsChevronUp, BsFillPencilFill, + BsPaperclip, BsPlusSquareFill, BsTruck, BsXLg, @@ -34,6 +35,10 @@ import { import { populateOpViajesEstatusSecuencia } from '../../store/features/Operaciones/OpViajes.Estatus.SecuenciaSlice' import { ViajeEstatus } from './Viaje/ViajeEstatus' import { useNavigate } from 'react-router-dom' +import DSCatUsuarios from '../../Services/Catalogos/CatUsuarios.Services' +import ICatUsuarios from '../../Interfaces/Catalogos/ICatUsuarios' +import { MFileManager } from '../Utils/MFileManager/MFileManager' +import '../../css/masterDetail.css' interface IProps {} @@ -52,6 +57,7 @@ export const OpMonitor: FC = (props) => { const [OpViajes, setOpViajes] = useState([]) const [showTripDialog, setShowTripDialog] = useState(false) const [ShowDeleteDialog, setShowDeleteDialog] = useState(false) + const [ShowDocumentsDialog, setShowDocumentsDialog] = useState(false) const [Switch, setSwitch] = useState(false) const [NoCaja, setNoCaja] = useState('') const [IDViaje, setIDViaje] = useState(0) @@ -66,6 +72,8 @@ export const OpMonitor: FC = (props) => { const [PickUpNumber, setPickUpNumber] = useState('') const [Hazmat, setHazmat] = useState(0) const [Search, setSearch] = useState('') + const [DataUsuarios, setDataUsuarios] = useState([]) + const [IDUsuario, setIDUsuario] = useState(0) const showInfo = (row: DTOOpViajes) => { setIDViaje(row.id) @@ -118,6 +126,18 @@ export const OpMonitor: FC = (props) => { const loadEveryting = () => { loadStatus() loadTrips() + loadUsers() + } + + const loadUsers = () => { + DSCatUsuarios.GetAll() + .then((response) => { + console.log(response.data) + setDataUsuarios(response.data) + }) + .catch((e: Error) => { + alert('Ocurrio un error' + e.message.toString()) + }) } useEffect(() => { @@ -208,7 +228,30 @@ export const OpMonitor: FC = (props) => { - + + { + setIDUsuario(parseInt(e.target.value)) + }} + value={IDUsuario} + className='form-select form-select-sm dialogLabel' + > + + {DataUsuarios + ? DataUsuarios.filter((row) => row.id > 1) + /* .sort((a, b) => a.nombre - b.nombre) */ + .map((row) => { + return ( + + ) + }) + : null} + + + = (props) => { Estatus Hazmat Ref AA + PickUp No Elimina Editar @@ -248,15 +292,16 @@ export const OpMonitor: FC = (props) => { {AllTrips ? AllTrips.filter( (MasterData) => - MasterData.id.toString().toLowerCase().includes(Search.toLowerCase()) || - MasterData.sCliente.toLowerCase().includes(Search.toLowerCase()) || - MasterData.sTipoOperacion.toLowerCase().includes(Search.toLowerCase()) || - MasterData.sOrigen.toLowerCase().includes(Search.toLowerCase()) || - MasterData.sDestino.toLowerCase().includes(Search.toLowerCase()) || - MasterData.sTipoUnidad.toLowerCase().includes(Search.toLowerCase()) || - MasterData.noCaja.toLowerCase().includes(Search.toLowerCase()) || - MasterData.refAgenciaAduanal.toLowerCase().includes(Search.toLowerCase()) || - MasterData.pickUpNumber.toLowerCase().includes(Search.toLowerCase()) + (IDUsuario !== 0 ? MasterData.usuario === IDUsuario : 1 === 1) && + (MasterData.id.toString().toLowerCase().includes(Search.toLowerCase()) || + MasterData.sCliente.toLowerCase().includes(Search.toLowerCase()) || + MasterData.sTipoOperacion.toLowerCase().includes(Search.toLowerCase()) || + MasterData.sOrigen.toLowerCase().includes(Search.toLowerCase()) || + MasterData.sDestino.toLowerCase().includes(Search.toLowerCase()) || + MasterData.sTipoUnidad.toLowerCase().includes(Search.toLowerCase()) || + MasterData.noCaja.toLowerCase().includes(Search.toLowerCase()) || + MasterData.refAgenciaAduanal.toLowerCase().includes(Search.toLowerCase()) || + MasterData.pickUpNumber.toLowerCase().includes(Search.toLowerCase())) ).map((MasterData) => { return ( <> @@ -347,6 +392,17 @@ export const OpMonitor: FC = (props) => { > {MasterData.refAgenciaAduanal} + { + setIDViaje(MasterData.id) + setShowDocumentsDialog(true) + }} + > + + + + = (props) => { + + { + setShowDocumentsDialog(false) + }} + dialogClassName='modal-90w' + backdrop='static' + > + + Documentos del viaje + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } diff --git a/src/Components/ResetAccount/ResetAccount.tsx b/src/Components/ResetAccount/ResetAccount.tsx new file mode 100644 index 0000000..4e24dd9 --- /dev/null +++ b/src/Components/ResetAccount/ResetAccount.tsx @@ -0,0 +1,123 @@ +import { FC, useEffect, useState } from 'react' +import { Link, Navigate, useNavigate } from 'react-router-dom' +import IAuth from '../../Interfaces/Auth/IAuth' +import DSAuth from '../../Services/Auth/Auth.Services' +import DSUsers from '../../Services/Catalogos/CatUsuarios.Services' +import { useDispatch, useSelector } from 'react-redux' +import { populateUserProfile } from '../../store/features/Auth/UserProfileSlice' +import { populateMenuItems } from '../../store/features/Auth/MenuItemsSlice' +import { RootState } from '../../store/store' +import Leyend from '../../images/leyend.png' +import jwt_decode from 'jwt-decode' + +interface IProps {} + +export const ResetAccount: FC = (props) => { + const navigate = useNavigate() + const dispatch = useDispatch() + const UserLogued = useSelector((state: RootState) => state.UserProfile.UserProfile.logueado) + const [Usuario, setUsuario] = useState('') + const [Contrasena, setContrasena] = useState('') + const [Contrasena2, setContrasena2] = useState('') + const [token, setToken] = useState( + (window.localStorage.getItem('token') ? window.localStorage.getItem('token') : '')! + ) + const [menuStr, setMenuStr] = useState( + (window.localStorage.getItem('menu') ? window.localStorage.getItem('menu') : '')! + ) + + const Reset = async () => { + if (Contrasena !== Contrasena2) { + alert('Las contraseñas no coinciden entre si') + return false + } + const data: IAuth = { + usuario: Usuario, + contrasena: Contrasena, + } + DSAuth.ResetPassword(data) + .then((response) => { + console.log(response.data) + alert('El cambio de contraseña se realizo exitosamente!') + navigate('/', { replace: true }) + }) + .catch((e: Error) => { + alert('Credenciales invalidas! ') + }) + } + + const validateToken = () => { + DSAuth.Validate() + .then((response) => { + navigate('/login', { replace: true }) + }) + .catch((e: Error) => { + alert('Ocurrio un error!') + }) + } + + useEffect(() => { + if (token.length > 5) { + validateToken() + } + }, [token]) + + return ( + + ) +} diff --git a/src/Components/Utils/MFileManager/MFileManager.tsx b/src/Components/Utils/MFileManager/MFileManager.tsx new file mode 100644 index 0000000..39e5425 --- /dev/null +++ b/src/Components/Utils/MFileManager/MFileManager.tsx @@ -0,0 +1,333 @@ +import React, { FC, useEffect, useState } from 'react' +import MFileManagerDS from '../../../Services/Utils/MFileManager.Service' +import IFileManager from '../../../Interfaces/Utils/IFileManager' +import { Alert, Button, Card, Col, ListGroup, Modal, Row } from 'react-bootstrap' +import { IconContext } from 'react-icons' +import { BsFillXCircleFill } from 'react-icons/bs' +import { MsgInformativo } from '../Toast/MsgInformativo' +import { TargetURL } from '../../../Constants/TargetURL' + +interface IProps { + IDTrafico: number + Proceso: number + Leyenda?: string + showPreview: number + fileType: string + canEdit: boolean +} + +export const MFileManager: FC = (props) => { + const [UserId, setUserId] = useState(() => { + const stickyValue = window.localStorage.getItem('UserId') + return stickyValue !== null ? JSON.parse(stickyValue) : 0 + }) + const [IDTrafico, setIDTrafico] = useState(props.IDTrafico ? props.IDTrafico : 0) + const [Proceso, setProceso] = useState(props.Proceso ? props.Proceso : 0) + const [ListaArchivos, setListaArchivos] = useState() + const [NombreArchivo, setNombreArchivo] = useState('') + const [MsgDialogDelete, setMsgDialogDelete] = useState(false) + const [IDArchivo, setIDArchivo] = useState(0) + const [header, setHeader] = useState('') + const [show, setShowMsg] = useState(false) + const [msg, setMsg] = useState('') + const URL = new TargetURL() + const msgColor = 'primary' + + const selectedFiles = (selectorFiles: any) => { + var formData = new FormData() + for (let i = 0; i < selectorFiles.files.length; i++) { + formData.append('FileList', selectorFiles.files[i]) + } + MFileManagerDS.Append(formData, IDTrafico, Proceso, UserId) + .then((response) => { + setListaArchivos(response.data) + }) + .catch((e: Error) => { + alert('Ocurrio un error: ' + e) + return + }) + return false + } + + useEffect(() => { + setIDTrafico(props.IDTrafico) + setProceso(props.Proceso) + MFileManagerDS.Get(props.IDTrafico, props.Proceso) + .then((response) => { + setListaArchivos(response.data) + }) + .catch((e: Error) => { + alert('Ocurrio un error: ' + e) + return + }) + }, [props.IDTrafico, props.Proceso]) + + const confirmDelete = (row: IFileManager) => { + setIDArchivo(row.id) + setNombreArchivo(row.nombreArchivo) + setMsgDialogDelete(true) + } + + const deleteItem = () => { + MFileManagerDS.DeleteFile(IDArchivo) + .then((response) => { + var arrArchivos = ListaArchivos!.filter(function (el) { + return el.id !== IDArchivo + }) + setListaArchivos(arrArchivos) + setHeader('Informtivo') + setMsg(response.data.Respuesta) + setMsgDialogDelete(false) + setShowMsg(false) + return + }) + .catch((e: Error) => { + setHeader('Error') + setMsg('Ocurrio un error, no se elimino el archivo') + setMsgDialogDelete(false) + setShowMsg(false) + return + }) + return false + } + + const getFileContent = (row: IFileManager) => { + MFileManagerDS.getFileContentById(row.id, row.proceso) + .then((response: any) => { + if (response.status === 200) { + if (row.nombreArchivo.toLowerCase().endsWith('.pdf')) { + // console.log(response.data) + const blob = new Blob([response.data], { type: 'application/pdf' }) + const url = window.URL.createObjectURL(blob) + window.open(url) + } else if ( + row.nombreArchivo.toLowerCase().endsWith('.png') || + row.nombreArchivo.toLowerCase().endsWith('.jpg') + ) { + const blob = new Blob([response.data], { type: 'image/png' }) + const url = window.URL.createObjectURL(blob) + window.open(url) + } else { + const url = window.URL.createObjectURL(new Blob([response.data])) + const link = document.createElement('a') + link.href = url + link.setAttribute('download', NombreArchivo ? NombreArchivo : 'Archivo.zip') + document.body.appendChild(link) + link.click() + } + } else { + setHeader('Error') + setMsg('A este concepto no se le ha anexado PDF') + setShowMsg(true) + return + } + }) + .catch((e: Error) => { + setHeader('Error') + setMsg('A este concepto no se le ha anexado PDF') + setShowMsg(true) + return + }) + } + + return ( +
+
+ + + + {props.Leyenda ? props.Leyenda : ''}    + {props.canEdit ? ( + selectedFiles(e.target)} + /> + ) : ( + '' + )} + + {ListaArchivos && props.showPreview === 1 ? ( +
+ + {ListaArchivos + ? ListaArchivos.map((rec) => { + return ( + + + + + + + {'Imagen'} { + getFileContent(rec) + }} + /> + + + + { + confirmDelete(rec) + }} + style={{ cursor: 'pointer' }} + /> + + + + + +   + + + {rec.nombreArchivo} + + + + + ) + }) + : ''} + +
+ ) : ListaArchivos && props.showPreview === 2 ? ( +
+ + {ListaArchivos + ? ListaArchivos.map((rec) => { + return ( + + + + + + {rec.nombreArchivo} + + + { + confirmDelete(rec) + }} + style={{ cursor: 'pointer' }} + /> + + + + + + + + ) + }) + : ''} + +
+ ) : ListaArchivos && props.showPreview === 3 ? ( +
+ {ListaArchivos + ? ListaArchivos.map((rec) => { + return ( + + + + + { + getFileContent(rec) + }} + > + {rec.nombreArchivo.substring(0, 40)} + + + + + + { + confirmDelete(rec) + }} + style={{ cursor: 'pointer' }} + /> + + + + + + + ) + }) + : ''} +
+ ) : ( + '' + )} +
+
+ setMsgDialogDelete(false)} size='lg'> + +
+ Favor de confirmar + +   + + + ¿Esta seguro de eliminar el archivo:
+
{NombreArchivo}? +
+
+
+ + + + + + +   + + + + + + +
+ { + setShowMsg(false) + }} + /> +
+ ) +} diff --git a/src/Components/Utils/Toast/MsgInformativo.tsx b/src/Components/Utils/Toast/MsgInformativo.tsx new file mode 100644 index 0000000..ca41a64 --- /dev/null +++ b/src/Components/Utils/Toast/MsgInformativo.tsx @@ -0,0 +1,51 @@ +import React, { FC } from 'react' +import { Toast, ToastContainer } from 'react-bootstrap' +import { IconContext } from 'react-icons' +import { BsFillExclamationSquareFill, BsXOctagonFill } from 'react-icons/bs' + +interface IProps { + show: boolean + msg: string + header: string + msgColor: string + time?: number + closeToast: (arg: boolean) => void +} + +export const MsgInformativo: FC = (props) => { + return ( +
+ + { + props.closeToast(false) + }} + > + + + {props.header.toString().includes('Error') ? ( + + + + ) : null} + {props.header.toString().includes('Informativo') ? ( + + + + ) : null} +   + {props.header} + + + +
{props.msg}
+
+
+
+
+ ) +} diff --git a/src/Interfaces/Catalogos/ICatUsuarios.ts b/src/Interfaces/Catalogos/ICatUsuarios.ts new file mode 100644 index 0000000..5725082 --- /dev/null +++ b/src/Interfaces/Catalogos/ICatUsuarios.ts @@ -0,0 +1,9 @@ +export default interface ICatUsuarios { + id: number + usuario: string + nombre: string + contrasena: string, + tipoUsuario : number + activo: number + idPerfil: number +} \ No newline at end of file diff --git a/src/Interfaces/Utils/IFileManager.ts b/src/Interfaces/Utils/IFileManager.ts new file mode 100644 index 0000000..5186c25 --- /dev/null +++ b/src/Interfaces/Utils/IFileManager.ts @@ -0,0 +1,9 @@ +export default interface IFileManager { + id: number, + idUsuario: number, + proceso: number, + nombreArchivo: string, + fechaRegistro: string, + tags: string, + size: number, +} \ No newline at end of file diff --git a/src/Services/Auth/Auth.Services.ts b/src/Services/Auth/Auth.Services.ts index f181bc7..8b35f50 100644 --- a/src/Services/Auth/Auth.Services.ts +++ b/src/Services/Auth/Auth.Services.ts @@ -2,7 +2,7 @@ import http from "../../Services/Auth/config/http-common"; import IAuth from "../../Interfaces/Auth/IAuth" //import ItemMenuData from "../../Interfaces/Auth/IMenu"; import IPermisos from "../../Interfaces/Auth/IPermisos"; -//import Token from '../../Interfaces/token' +import IRespuesta from "../../Interfaces/Respuestas/IRespuesta"; class authDataService { async login(data: IAuth) { @@ -11,5 +11,8 @@ class authDataService { async Validate() { return await http.get("Auth/Validate"); } + ResetPassword(data: IAuth) { + return http.post(`Auth/ResetPassword`, data); + } } export default new authDataService(); \ No newline at end of file diff --git a/src/Services/Catalogos/CatUsuarios.Services.ts b/src/Services/Catalogos/CatUsuarios.Services.ts new file mode 100644 index 0000000..8f059d6 --- /dev/null +++ b/src/Services/Catalogos/CatUsuarios.Services.ts @@ -0,0 +1,17 @@ +import http from "../../Services/Auth/config/http-common"; +import ICatUsuarios from "../../Interfaces/Catalogos/ICatUsuarios"; +import IRespuesta from "../../Interfaces/Respuestas/IRespuesta"; +import IAuth from "../../Interfaces/Auth/IAuth"; + +class CatUsuariosDataService { + async GetAll() { + return await http.get("Catalogos/Usuarios/GetAll"); + } + Append(data: ICatUsuarios) { + return http.post(`Catalogos/Usuarios/Append`,data); + } + Delete(id: number) { + return http.delete(`Catalogos/Usuarios/Delete/${id}`); + } +} +export default new CatUsuariosDataService(); \ No newline at end of file diff --git a/src/Services/Utils/MFileManager.Service.ts b/src/Services/Utils/MFileManager.Service.ts new file mode 100644 index 0000000..adc32d0 --- /dev/null +++ b/src/Services/Utils/MFileManager.Service.ts @@ -0,0 +1,26 @@ +import http from "../Auth/config/http-common"; +import IRespuesta from "../../Interfaces/Respuestas/IRespuesta" +import IFileManager from "../../Interfaces/Utils/IFileManager"; + +class MFileManagerDataService { + + Append(formData: any, IDTrafico: number, Proceso: number, Usuario: number) { + return http.post(`/Utils/MFileManager/Append?Tags=${IDTrafico}&Proceso=${Proceso}&Usuario=${Usuario}`, formData) + } + DeleteFile(id: number) { + return http.delete(`/FileManager/DeleteById/${id}`); + } + Get(IDTrafico: number, Proceso: number) { + return http.get(`/Utils/MFileManager/GetFilesFromLog?Tags=${IDTrafico}&Proceso=${Proceso}`) + } + getFileContentById(id: number, Proceso: number) { + return http.get(`/Utils/MFileManager/GetFileContentById?id=${id}&Proceso=${Proceso}`, {responseType: 'arraybuffer'}) + .then(function (response) { + return response + }) + .catch(function (error) { + console.log(error) + }) + } +} +export default new MFileManagerDataService(); \ No newline at end of file diff --git a/src/css/masterDetail.css b/src/css/masterDetail.css index 1d4a46f..f0850f9 100644 --- a/src/css/masterDetail.css +++ b/src/css/masterDetail.css @@ -188,3 +188,9 @@ position: relative; */ display: inline-block; } + +.modal-90w { + width: 90%; + max-width: none !important; + max-height: none !important; +} diff --git a/src/images/Clean.png b/src/images/Clean.png new file mode 100644 index 0000000..ca15cd4 Binary files /dev/null and b/src/images/Clean.png differ