Skip to content

Commit 9722c59

Browse files
committed
Add header view
1 parent 87dd2c4 commit 9722c59

File tree

9 files changed

+163
-0
lines changed

9 files changed

+163
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react';
2+
3+
export function Icons() {
4+
return (
5+
<svg style={{ display: 'none' }}>
6+
<symbol id="icon-last-access-at" viewBox="0 0 448 512">
7+
<path d="M96 32l0 32L48 64C21.5 64 0 85.5 0 112l0 48 448 0 0-48c0-26.5-21.5-48-48-48l-48 0 0-32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 32L160 64l0-32c0-17.7-14.3-32-32-32S96 14.3 96 32zM448 192L0 192 0 464c0 26.5 21.5 48 48 48l352 0c26.5 0 48-21.5 48-48l0-272z"></path>
8+
</symbol>
9+
<symbol id="icon-total-access" viewBox="0 0 384 512">
10+
<path d="M64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-384c0-35.3-28.7-64-64-64L64 0zM96 64l192 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32L96 160c-17.7 0-32-14.3-32-32l0-32c0-17.7 14.3-32 32-32zm32 160a32 32 0 1 1 -64 0 32 32 0 1 1 64 0zM96 352a32 32 0 1 1 0-64 32 32 0 1 1 0 64zM64 416c0-17.7 14.3-32 32-32l96 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-96 0c-17.7 0-32-14.3-32-32zM192 256a32 32 0 1 1 0-64 32 32 0 1 1 0 64zm32 64a32 32 0 1 1 -64 0 32 32 0 1 1 64 0zm64-64a32 32 0 1 1 0-64 32 32 0 1 1 0 64zm32 64a32 32 0 1 1 -64 0 32 32 0 1 1 64 0zM288 448a32 32 0 1 1 0-64 32 32 0 1 1 0 64z"></path>
11+
</symbol>
12+
<symbol id="icon-audio" viewBox="0 0 640 512">
13+
<path d="M533.6 32.5C598.5 85.2 640 165.8 640 256s-41.5 170.7-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z"></path>
14+
</symbol>
15+
</svg>
16+
);
17+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.access-info-container {
2+
display: flex;
3+
flex-direction: row;
4+
align-items: center;
5+
justify-content: flex-left;
6+
gap: 1em;
7+
}
8+
.access-info-container > .access-info {
9+
display: flex;
10+
flex-direction: row;
11+
align-items: center;
12+
justify-content: center;
13+
gap: 0.5em;
14+
}
15+
.icon {
16+
width: 1em;
17+
height: 1em;
18+
}
19+
.access-info > .access-content {
20+
margin-top: 0.35em;
21+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { render, screen } from '@testing-library/react';
2+
import React from 'react';
3+
import { SearchResultAccessSummary } from './SearchResultAccessSummary';
4+
5+
test('access-summary block should show last access date and total access for searched word', async () => {
6+
const accessSummary = {
7+
lastAccessAt: '2024-01-01T00:00:00.000Z',
8+
totalAccess: 313,
9+
};
10+
11+
render(<SearchResultAccessSummary accessSummary={accessSummary} />);
12+
13+
expect(screen.getByText('2024-01-01 00:00:00')).toBeDefined();
14+
expect(screen.getByText('313')).toBeDefined();
15+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import { Utils } from '../../../lib/utils';
3+
import { Icons } from '../../icons.svg';
4+
import { AccessSummary } from '../../types';
5+
import './SearchResultAccessSummary.css';
6+
7+
export function SearchResultAccessSummary({
8+
accessSummary,
9+
}: {
10+
accessSummary: AccessSummary;
11+
}) {
12+
return (
13+
<div className="access-info-container" data-testid="access-summary">
14+
<div className="access-info">
15+
<Icons />
16+
<div className="icon">
17+
<svg>
18+
<use id="#icon-last-access-at" />
19+
</svg>
20+
</div>
21+
<span className="access-content">
22+
{new Utils().iso8601ToDateTime(accessSummary.lastAccessAt)}
23+
</span>
24+
<div className="icon">
25+
<svg>
26+
<use id="#icon-total-access" />
27+
</svg>
28+
</div>
29+
<span className="access-content">{accessSummary.totalAccess}</span>
30+
</div>
31+
</div>
32+
);
33+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.header-container {
2+
display: flex;
3+
flex-direction: row;
4+
align-items: center;
5+
justify-content: space-between;
6+
gap: 2em;
7+
}
8+
.word {
9+
padding: 0.5em 0;
10+
max-width: 60%;
11+
word-wrap: anywhere;
12+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { render, screen } from '@testing-library/react';
2+
import React from 'react';
3+
import { SearchResultHeader } from './SearchResultHeader';
4+
5+
test('header should have the searched word and access-summary section (when provided)', async () => {
6+
const searchedWord = 'hello';
7+
const accessSummary = {
8+
totalAccess: 3,
9+
lastAccessAt: '2024-01-01T00:00:00.000Z',
10+
};
11+
12+
render(
13+
<SearchResultHeader word={searchedWord} accessSummary={accessSummary} />,
14+
);
15+
16+
const word = screen.getByText(searchedWord);
17+
const accessSummaryBlock = screen.queryByTestId('access-summary');
18+
19+
expect(word).toBeDefined();
20+
expect(accessSummaryBlock).not.toBeNull();
21+
});
22+
23+
test('header should not have any access-summary section for first time access of a word', async () => {
24+
const searchedWord = 'hello';
25+
26+
render(<SearchResultHeader word={searchedWord} accessSummary={null} />);
27+
28+
const accessSummaryBlock = screen.queryByTestId('access-summary');
29+
30+
expect(accessSummaryBlock).toBeNull();
31+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import { AccessSummary } from '../../types';
3+
import { SearchResultAccessSummary } from './SearchResultAccessSummary';
4+
import './SearchResultHeader.css';
5+
6+
export function SearchResultHeader({
7+
word,
8+
accessSummary,
9+
}: {
10+
word: string;
11+
accessSummary: AccessSummary;
12+
}) {
13+
return (
14+
<div className="header-container" data-testid="header-container">
15+
<div className="word">{word}</div>
16+
{accessSummary && (
17+
<SearchResultAccessSummary accessSummary={accessSummary} />
18+
)}
19+
</div>
20+
);
21+
}

views/interactivity/src/lib/utils.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,11 @@ describe('decodeBase64Gzip()', () => {
2424
expect(uncompressed).toBe('some text');
2525
});
2626
});
27+
28+
describe('iso8601ToDateTime()', () => {
29+
test('function should return datetime string when iso 8601 datetime is given', () => {
30+
expect(utils.iso8601ToDateTime('2024-01-01T00:00:00.000Z')).toBe(
31+
'2024-01-01 00:00:00',
32+
);
33+
});
34+
});

views/interactivity/src/lib/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,9 @@ export class Utils {
77
);
88
return pako.inflate(binaryData, { to: 'string' });
99
}
10+
11+
iso8601ToDateTime(iso8601: string) {
12+
// iso 8601: YYYY-MM-DDThh:mm:ss.SSSZ
13+
return iso8601.split('.')[0].replace('T', ' ');
14+
}
1015
}

0 commit comments

Comments
 (0)