Skip to content

Commit

Permalink
Global search
Browse files Browse the repository at this point in the history
  • Loading branch information
mike-north committed Feb 20, 2018
1 parent 0382e28 commit e091e4b
Show file tree
Hide file tree
Showing 17 changed files with 237 additions and 30 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
"db:setup:pg": "SCRIPTY_SILENT=true scripty",
"db:setup:sqlite": "SCRIPTY_SILENT=true scripty",
"db:migrate:pg": "scripty",
"db:migrate:heroku:pg":
"DATABASE_URL=$DATABASE_URL?ssl=true npm run db:migrate:pg --- up",
"db:migrate:heroku:pg": "DATABASE_URL=$DATABASE_URL?ssl=true npm run db:migrate:pg --- up",
"db:migrate:sqlite": "scripty",
"db:migrate:create": "SCRIPTY_SILENT=true scripty"
},
Expand All @@ -40,6 +39,7 @@
"lodash": "~4.17.4",
"moment": "^2.20.1",
"scripty": "^1.7.2",
"server-timing": "^1.1.2",
"sql-formatter": "^2.1.2",
"sqlite": "^2.9.1",
"sqlite3": "^3.1.13",
Expand Down
67 changes: 67 additions & 0 deletions public/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,29 @@ a.mui-btn.disabled {
background: navy;
}

.main-nav .nav .nav-item button {
display: inline-block;
text-transform: uppercase;
line-height: 64px;
padding: 0 10px;
background: transparent;
border: none;
box-shadow: none;
}

.main-nav .nav .nav-item a,
.main-nav .nav .nav-item button {
color: white;
transition: color 0.2s ease-in-out;
}

.main-nav.search-mode .nav .nav-item a {
color: #55b;
}
.main-nav.search-mode .nav .nav-item button {
color: yellow;
}

.table-col-sort a.sort-button {
color: grey;
}
Expand Down Expand Up @@ -180,6 +203,50 @@ table .actions {
color: white;
}

.main-nav {
transition-property: 'background', 'margin-bottom', 'color';
transition-duration: 0.2s;
transition-timing-function: ease-in-out;
margin-bottom: 0;
}
.main-nav.search-mode {
background: navy;
color: #aaa;
margin-bottom: 60px;
}
.main-nav .search-box {
transform: translateY(-500px);
opacity: 0;
height: 0;
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
background: navy;
}
.main-nav.search-mode .search-box {
transform: translateY(0);
opacity: 1;
}
.main-nav .search-box .search-input-container {
border-left: 10px solid navy;
border-right: 10px solid navy;
border-bottom: 10px solid navy;
background: #222299;
}
.main-nav .search-box .search-input-container .search-input {
color: white;
border-bottom-color: #aaa;
}
.main-nav .search-box .search-input-container .mui-textfield label {
padding-top: 5px;
color: #aaa;
}
.main-nav .search-box .search-input-container .mui-textfield {
background: transparent;
margin-left: 5px;
margin-right: 5px;
margin-bottom: 10px;
padding-top: 20px;
}

input[type='range'] {
-webkit-appearance: none;
width: 100%;
Expand Down
26 changes: 26 additions & 0 deletions public/js/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
(function() {
'use strict';
let searchButton = document.querySelector('.global-search');

searchButton.addEventListener('click', function() {
let isSearch = document
.querySelector('.main-nav')
.classList.contains('search-mode');
if (!isSearch) {
document.querySelector('.main-nav').classList.add('search-mode');
document.querySelector('.main-nav .search-input').focus();
} else {
document.querySelector('.main-nav').classList.remove('search-mode');
document.querySelector('.main-nav .search-input').value = null;
}
});

document
.querySelector('.main-nav .search-input')
.addEventListener('keydown', function(/** @type {KeyboardEvent}*/ evt) {
if (evt.keyCode === 27 /* esc */) {
document.querySelector('.main-nav').classList.remove('search-mode');
evt.target.value = null;
}
});
})();
2 changes: 1 addition & 1 deletion src/data/products.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getDb } from '../db/utils';
import { sql } from '../sql-string';

interface ProductFlavorFilter {
export interface ProductFlavorFilter {
flavorName: string;
level: number;
type: 'less-than' | 'greater-than';
Expand Down
22 changes: 22 additions & 0 deletions src/data/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getDb } from '../db/utils';
import { sql } from '../sql-string';

export async function getSearchResults(term: string): Promise<any[]> {
let db = await getDb();
return db.all(
sql`
SELECT * from
(SELECT 'product' as entity, productname as name, productname as the_text, ('' || id) as id FROM Product
UNION
SELECT 'supplier' as entity, companyname as name, companyname as the_text, ('' || id) as id FROM Supplier
UNION
SELECT 'customer' as entity, companyname as name, companyname as the_text, ('' || id) as id FROM Customer
UNION
SELECT 'category' as entity, categoryname as name, categoryname as the_text, ('' || id) as id FROM Category
UNION
SELECT 'employee' as entity, (firstname || ' ' || lastname) as name, (firstname || ' ' || lastname) as the_text, ('' || id) as id FROM Employee
) as a
WHERE lower(a.the_text) LIKE $1`,
`%${term}%`
);
}
35 changes: 23 additions & 12 deletions src/db/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ export interface SQLStatement {
all<T = any>(...params: any[]): Promise<T[]>;
}

function timeVal(ms: number) {
if (ms < 1) {
return `${(ms * 1000).toPrecision(4)}μs`;
} else if (ms < 1000) {
return `${ms.toPrecision(4)}ms`;
} else {
return `${(ms / 1000).toPrecision(4)}s`;
}
}

export abstract class SQLDatabase<S extends SQLStatement = any> {
// tslint:disable-next-line:no-empty
public static async setup(): Promise<SQLDatabase<any>> {
Expand Down Expand Up @@ -46,34 +56,35 @@ export abstract class SQLDatabase<S extends SQLStatement = any> {
});
}

protected logQuery(
query: string,
params: JSONArray,
[begin, end]: [number, number]
) {
let t = end - begin;
protected logQuery(query: string, params: JSONArray, t: number) {
let timestring =
t < 1000000
? chalk.yellow(`${(t / 1000000).toPrecision(4)}ms`)
: chalk.bgRed.white(`${(t / 1000000).toPrecision(4)}ms`);
t < 5
? chalk.bgGreenBright.black(timeVal(t))
: t < 100
? chalk.bgYellowBright.black(timeVal(t))
: chalk.bgRedBright.white(timeVal(t));
logger.info(
[
this.colorizeQuery(query) + ` (${timestring})`,
`${chalk.grey('PARAMS:')} ${JSON.stringify(params)}`
].join('\n')
);
return t;
}
protected async measure<T>(
query: string,
params: JSONArray,
fn: () => Promise<T>
): Promise<T> {
let [, begin] = process.hrtime();
let begin = process.hrtime();
try {
let result = await fn();
let t = -1;
if (process.env.NODE_ENV !== 'test') {
let [, end] = process.hrtime();
this.logQuery(query, params, [end, begin]);
let end = process.hrtime();
let diff = [end[0] - begin[0], end[1] - begin[1]];
t = diff[0] * 1000 + diff[1] / 1000000;
this.logQuery(query, params, t);
}
return result;
} catch (e) {
Expand Down
12 changes: 1 addition & 11 deletions src/db/sqlite-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,7 @@ export default class SQLiteDB extends SQLDatabase<sqlite.Statement> {
if (process.env.NODE_ENV !== 'test') {
// tslint:disable-next-line:no-shadowed-variable
this.db.on('profile', (sql: string, time: number) => {
this.logQuery(sql, [], [0, time * 1000000]);
// logger.info(
// [
// `
// ${chalk.magentaBright('>>')} ${highlight(sql.trim(), {
// language: 'sql',
// ignoreIllegals: true
// })}`,
// `(${chalk.yellow(`${time.toPrecision(2)}ms`)})`
// ].join(' ')
// );
this.logQuery(sql, [], time);
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/express-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PORT } from './constants';
import * as bodyParser from 'body-parser';
import { loadHandlebarsHelpers } from './load-helpers';
import { logger } from './log';
import * as serverTiming from 'server-timing';
import router from './routers/main';

async function startListening(app: express.Express): Promise<http.Server> {
Expand Down Expand Up @@ -52,6 +53,7 @@ function installMiddlewares(app: express.Application) {
}
})
);
app.use(serverTiming());
}

async function setupRouting(app: express.Application) {
Expand Down
14 changes: 14 additions & 0 deletions src/helpers/entity-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default function entityUrl(entity: string, id: string) {
switch (entity) {
case 'employee':
return `/employees/${id}`;
case 'customer':
return `/customers/${id}`;
case 'supplier':
return `/supplier/${id}`;
case 'product':
return `/product/${id}`;
default:
throw new Error(`No URL-building strategy for ${entity}`);
}
}
2 changes: 2 additions & 0 deletions src/routers/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import customerRouter from './customers';
import employeeRouter from './employees';
import orderRouter from './orders';
import supplierRouter from './suppliers';
import searchRouter from './search';
import productRouter from './products';
import {
getEmployeeSalesLeaderboard,
Expand Down Expand Up @@ -43,5 +44,6 @@ router.use('/customers', customerRouter);
router.use('/suppliers', supplierRouter);
router.use('/orders', orderRouter);
router.use('/products', productRouter);
router.use('/search', searchRouter);

export default router;
16 changes: 13 additions & 3 deletions src/routers/orders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,21 @@ function normalizeOrderDetails(raw: {

router.get('/', async (req, res, next) => {
try {
let { page = 1, perPage, sort, order } = req.query;
let orders = await getAllOrders({ page, perPage, sort, order });
let { page = 1, perPage = 20 } = req.query;
let opts: { [k: string]: number | string } = {
page: req.query.page || 1,
perPage: req.query.perPage || 20
};
if (typeof req.query.sort === 'string' && req.query.sort !== '') {
opts.sort = req.query.sort;
}
if (typeof req.query.order === 'string' && req.query.order !== '') {
opts.order = req.query.order;
}
let orders = await getAllOrders(opts);
res.render('orders', { orders, page });
} catch (e) {
next();
next(e);
}
});

Expand Down
16 changes: 16 additions & 0 deletions src/routers/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as express from 'express';
import { getSearchResults } from '../data/search';
import { logger } from '../log';

const router = express.Router();

router.get('/', async (req, res, next) => {
try {
let results = await getSearchResults(req.param('s').toLowerCase());
res.render('search', { results });
} catch (e) {
next(e);
}
});

export default router;
1 change: 1 addition & 0 deletions src/types/server-timing.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'server-timing';
1 change: 1 addition & 0 deletions views/layouts/main.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<script src="/static/js/mui.min.js"></script>
<script src="/static/js/jquery-3.3.1.min.js"></script>
<script src="/static/js/selectize.min.js"></script>
<script src="/static/js/app.js"></script>
</body>

</html>
18 changes: 17 additions & 1 deletion views/partials/appbar.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<div class="mui-appbar">
<div class="mui-appbar main-nav">
<div class="mui-container">
<ul class="nav">
<li class='nav-item'>
{{#link-to classNames='mui--appbar-height' href="/"}} Dashboard {{/link-to}}
</li>
<li class='nav-item'>
{{#link-to classNames='mui--appbar-height'
href="/employees"}}
Expand Down Expand Up @@ -31,6 +34,19 @@
Products
{{/link-to}}
</li>
<li class='nav-item'>
<button class='mui--appbar-height global-search'>Search</button>
</li>
</ul>
</div>
<div class="search-box">
<div class="mui-container">
<form action='/search' class="search-input-container mui-form">
<div class="mui-textfield mui-textfield--float-label">
<input type='search' name='s' class='search-input' type="text">
<label for="s">Search</label>
</div>
</form>
</div>
</div>
</div>
Loading

0 comments on commit e091e4b

Please sign in to comment.