Fakultas Ilmu Komputer UI

Commit 46fa99ec authored by Sean Zeliq Urian's avatar Sean Zeliq Urian
Browse files

[RED] Seperate List and Tabel Component

parent 03c7e009
import React, { useContext, useEffect, useState } from 'react';
import styled, { ThemeContext } from 'styled-components';
import {
Box,
Text,
Gap,
Icon,
Field,
Button,
Checkbox,
} from 'components';
import { AppContext } from 'contexts';
import Loading from 'components/Loading';
import { DateProps } from 'contexts/AppContext/types';
import { Log } from 'scenes/ActivityLog/types/types';
import { generateLogDetail, generateLogMessage } from "scenes/ActivityLog/utilities/utils";
import { DEFAULT_THEME } from 'scenes/ActivityLog'
export default function ActivityList() {
const { colors } = useContext(ThemeContext) || DEFAULT_THEME;
const { services } = useContext(AppContext);
const [logList, setLogList] = useState<Log[]>([]);
const [page, setPage] = useState(1);
const [next, setNext] = useState(false);
const [prev, setPrev] = useState(false);
const [totalLog, setTotalLog] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const verticalLineHeight = (logList.length - 1) * 52;
const [roleFilterQuery, setRoleFilterQuery] = useState<String>("Semua Peran");
const [ignoreDateFilterQuery, setIgnoredateFilterQuery] = useState<boolean>(
true
);
const [dateFilterQuery, setDateFilterQuery] = useState<DateProps>({
start_date: new Date(),
end_date: new Date(),
});
const VerticalLine = styled.div`
border-left: 2px solid ${colors.mediumGray};
height: ${verticalLineHeight}px;
position: absolute;
left: 12px;
top: 5px;
`;
const Circle = styled.div`
left: 12px;
width: 24px;
height: 24px;
border-radius: 12px;
background: ${colors.mediumGray};
margin-right: 20px;
`;
const fetchLog = async (page: number, roleFilterQuery: String, dateFilterQuery: DateProps, ignoreDateFilterQuery: boolean) => {
let dateOnly = {
start_date: dateFilterQuery.start_date.toISOString().split('T')[0],
end_date: dateFilterQuery.end_date.toISOString().split('T')[0]
}
setIsLoading(true);
const logResponse = await services.main.getLog(page, roleFilterQuery, dateOnly.start_date, dateOnly.end_date, ignoreDateFilterQuery);
let logs: Log[] = [];
for (let log of logResponse.data.results) {
let logObject: Log = {
timestamp: new Date(log.recorded_at),
message: await generateLogMessage(log, services),
detail: await generateLogDetail(log, services),
};
logs.push(logObject);
}
setLogList(logs);
setNext(logResponse.data.next ? true : false);
setPrev(logResponse.data.previous ? true : false);
setTotalLog(logResponse.data.count);
setIsLoading(false);
};
useEffect(() => {
fetchLog(page, roleFilterQuery, dateFilterQuery, ignoreDateFilterQuery);
}, [page]);
return (
<>
<Box width="60%" margin="2em" axis={Box.Axis.Vertical}>
<Box width="30%" axis={Box.Axis.Horizontal}>
<Field
name="Dari"
type={Field.Type.Date}
value={dateFilterQuery.start_date}
updateValue={(val) => {
setDateFilterQuery({ ...dateFilterQuery, start_date: val });
}}
/>
<Gap axis={Gap.Axis.Horizontal} gap={20} />
<Field
name="Sampai"
type={Field.Type.Date}
value={dateFilterQuery.end_date}
updateValue={(val) => {
setDateFilterQuery({ ...dateFilterQuery, end_date: val });
}}
/>
<Gap axis={Gap.Axis.Horizontal} gap={20} />
<Box width="100%" height="75px" crossAxis="center">
<Checkbox
label="Abaikan rentang waktu"
isChecked={ignoreDateFilterQuery}
updateValue={(value) => {
setIgnoredateFilterQuery(value);
}}
/>
</Box>
</Box>
<Gap gap={50} axis={Gap.Axis.Horizontal} />
<Field
name="Filter peran pembuat aktivitas"
type={Field.Type.Dropdown}
placeholder={'Ketikan Admin atau Kader'}
value={roleFilterQuery}
values={[
{ label: 'Admin', value: 'Admin' },
{ label: 'Kader', value: 'Kader' },
{ label: 'Semua Peran', value: 'Semua Peran' },
]}
updateValue={(value) => setRoleFilterQuery(value)}
/>
<Gap gap={50} axis={Gap.Axis.Horizontal} />
<Button id="filter" onClick={() => fetchLog(page, roleFilterQuery, dateFilterQuery, ignoreDateFilterQuery)}>
Terapkan Filter
</Button>
</Box>
<Box width="100%" axis={Box.Axis.Vertical} margin="2em 2em">
{logList.length === 0 && isLoading === false ? (
<Box width="100%" crossAxis="center" mainAxis="center">
<Text color={colors.mediumGray}>
Belum ada aktivitas yang tercatat
</Text>
</Box>
) : (
<></>
)}
<VerticalLine />
{logList?.map((log: Log, index: number) => {
return (
<Box id="list-activity" width="100%" key={index}>
<Circle />
<Box width="80%" axis={Box.Axis.Vertical}>
<Text isBold>
{log.timestamp.toLocaleString('id-ID', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
})}
</Text>
<Text>
{log.message.length > 128
? log.message.slice(0, 125) + '...'
: log.message}
</Text>
<Gap axis={Gap.Axis.Vertical} gap={12}></Gap>
</Box>
</Box>
);
})}
</Box>
<Box width="100%" mainAxis="flex-end">
<Text color={colors.mediumGray}>
{((page - 1) * 10 + logList.length).toString() + ' dari ' + totalLog}
</Text>
<Gap gap={12} axis={Gap.Axis.Horizontal} />
<Icon
id="prev-button"
cursor="pointer"
onClick={prev ? () => setPage(page - 1) : () => { }}
src="/assets/icons/right-paging.svg"
origin="50% 50%"
height="16px"
opacity={prev ? 1 : 0.5}
transform="rotateZ(180deg)"
/>
<Gap gap={12} axis={Gap.Axis.Horizontal} />
<Text id="page-number">{`${page}`}</Text>
<Gap gap={12} axis={Gap.Axis.Horizontal} />
<Icon
id="next-button"
cursor="pointer"
onClick={next ? () => setPage(page + 1) : () => { }}
src="/assets/icons/right-paging.svg"
height="16px"
opacity={next ? 1 : 0.5}
/>
</Box>
<Loading isLoading={isLoading} shouldSetBlackTheme={true} />
</>
);
}
import React from 'react';
import axios from 'axios';
import renderer, { act } from 'react-test-renderer';
import ActivityLog from '.';
import { useMainService } from 'services';
import { AppContext } from 'contexts';
import { mount } from 'enzyme';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
const testProps = {
services: {
main: useMainService('dummyToken'),
},
};
it('renders correctly', () => {
const instance = renderer.create(
<ActivityLog />
);
expect(instance).toBeTruthy();
});
describe('load logs and generate log messages correctly', () => {
it('Monitoring case', () => {
mockedAxios.request.mockResolvedValue({
status: 200,
data: {
previous: null,
next: null,
count: 1,
results: [
{
action_type: "Create",
model_name: "Monitoring Case",
object_id: "1234",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
],
investigation_case: {
case_subject: {
name: "Test"
}
}
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityLog />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
});
it('Investigation case', () => {
mockedAxios.request.mockResolvedValue({
status: 200,
data: {
previous: null,
next: null,
count: 1,
results: [
{
action_type: "Create",
model_name: "Investigation Case",
object_id: "1234",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
],
case_subject: {
name: "Test"
}
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityLog />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
});
it('Case subject', () => {
mockedAxios.request.mockResolvedValue({
status: 200,
data: {
previous: null,
next: null,
count: 1,
results: [
{
action_type: "Create",
model_name: "Case Subject",
object_id: "1234",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
],
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityLog />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
});
it('Create account', () => {
mockedAxios.request.mockResolvedValue({
status: 200,
data: {
username: "Test",
is_admin: false,
previous: null,
next: null,
count: 1,
results: [
{
action_type: "Create",
model_name: "Account",
object_id: "1234",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
]
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityLog />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
});
it('Create account, but account deleted', () => {
mockedAxios.request.mockResolvedValue({
status: 200,
data: {
previous: null,
next: null,
count: 1,
results: [
{
action_type: "Create",
model_name: "Account",
object_id: "1234",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
]
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityLog />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
});
it('Edit account', () => {
mockedAxios.request.mockResolvedValue({
status: 200,
data: {
username: "Test",
is_admin: false,
previous: null,
next: null,
count: 1,
results: [
{
action_type: "Edit",
model_name: "Account",
object_id: "1234",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
]
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityLog />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
});
it('Edit account, but account deleted', () => {
mockedAxios.request.mockResolvedValue({
status: 404,
data: {
previous: null,
next: null,
count: 1,
results: [
{
action_type: "Edit",
model_name: "Account",
object_id: "1234",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
]
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityLog />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
});
it('Delete account', () => {
mockedAxios.request.mockResolvedValue({
status: 200,
data: {
username: "Test",
is_admin: false,
previous: null,
next: null,
count: 1,
results: [
{
action_type: "Delete",
model_name: "Account",
object_id: "1234",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
]
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityLog />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
});
});
it('fetch new logs and change page number when press next or previous button', () => {
mockedAxios.request.mockResolvedValue({
status: 200,
data: {
username: "Test",
is_admin: false,
previous: true,
next: true,
count: 1,
results: [
{
action_type: "Delete",
model_name: "Account",
object_id: "1234",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
]
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityLog />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
const prevButton = instance.find('#prev-button');
const nextButton = instance.find('#next-button');
let pageNumber = instance.find('Text').findWhere(elem => elem.prop('id') === 'page-number');
expect(pageNumber.text()).toBe('1');
nextButton.at(0).simulate('click');
expect(mockedAxios.request).toBeCalled();
prevButton.at(0).simulate('click');
pageNumber = instance.find('Text').findWhere(elem => elem.prop('id') === 'page-number');
expect(pageNumber.text()).toBe('1');
expect(mockedAxios.request).toBeCalled();
})
it('Sort and filter are displayed and clickable', () => {
mockedAxios.request.mockResolvedValue({
status: 200,
data: {
username: "Test",
is_admin: false,
previous: true,
next: true,
count: 1,
results: [
{
action_type: "Delete",
model_name: "Account",
object_id: "1234",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
]
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityLog />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
let categoryButton = instance.find('[data-test-id="button-Tabel"]');
categoryButton.at(0).simulate('click');
let sortByDate = instance.find("#sortbydate");
let sortByObj = instance.find("#sortbyobj");
let filter = instance.find("#filter");
sortByDate.simulate("click");
sortByObj.simulate("click");
filter.simulate("click");
let table = instance.find("Table");
expect(table).toBeTruthy();
});
\ No newline at end of file
import React, { useContext, useEffect, useState } from 'react';
import styled, { ThemeContext } from 'styled-components';
import { Box, Text, Content, Gap, Icon, Table, CategoryButton, Field, Button } from 'components';
import { AppContext } from 'contexts';
import Loading from 'components/Loading';
interface Log {
timestamp: Date;
message: string;
detail: LogDetail;
}
interface LogDetail {
activity: string;
authorRole: "admin" | "kader";
authorName: string;
object: string;
}
const DEFAULT_THEME = {
colors: {
totallyWhite: 'white',
mediumGray: 'gray',
},
};
export default function ActivityLog() {
const { colors } = useContext(ThemeContext) || DEFAULT_THEME;
const { services } = useContext(AppContext);
const [logList, setLogList] = useState<Log[]>([]);
const [logTable, setLogTable] = useState<Log[]>([]);
const [page, setPage] = useState(1);
const [next, setNext] = useState(false);
const [prev, setPrev] = useState(false);
const [totalLog, setTotalLog] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const verticalLineHeight = ((logList.length - 1) * 52);