mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-25 21:19:00 +01:00
actually revert this until its NOT broken (#23557)
This commit is contained in:
@@ -44,15 +44,12 @@ Make sure to add new items to this list if you document new components.
|
||||
- [`Table`](#table)
|
||||
- [`Table.Row`](#tablerow)
|
||||
- [`Table.Cell`](#tablecell)
|
||||
- [`Table.Sortable`](#tablesortable)
|
||||
- [`Tabs`](#tabs)
|
||||
- [`Tabs.Tab`](#tabstab)
|
||||
- [`Tooltip`](#tooltip)
|
||||
- [`tgui/layouts`](#tguilayouts)
|
||||
- [`Window`](#window)
|
||||
- [`Window.Content`](#windowcontent)
|
||||
- [`tgui/interfaces/common`](#tguiinterfacescommon)
|
||||
- [`RecordsTable`](#recordstable)
|
||||
|
||||
## General Concepts
|
||||
|
||||
@@ -847,103 +844,6 @@ A straight forward mapping to `<td>` element.
|
||||
- `collapsing: boolean` - Collapses table cell to the smallest possible size,
|
||||
and stops any text inside from wrapping.
|
||||
|
||||
### `Table.Sortable`
|
||||
|
||||
A managed sortable table.
|
||||
|
||||
**Props:**
|
||||
|
||||
- See inherited props: [Table](#table)
|
||||
- `columns: Column[]` - A list of data fields to be
|
||||
used. The order in which columns are specified is the order in which they
|
||||
appear in the UI.
|
||||
- `columnDefaults: UnnamedColumn` - Default values for all columns.
|
||||
`columnDefaults.datum.children` can be used to specify the default children,
|
||||
but will get overriden by children defined at each column.
|
||||
`columnDefaults.datum.props` and `columnDefaults.header.props` can be used to
|
||||
specify additional props for the datum and header respectively.
|
||||
- `data: object[]` - The data to put into the table.
|
||||
- `datumID: object => object` - A function which takes in a datum and returns
|
||||
an id.
|
||||
- `filter: object[] => object[]` - A function applied to the data before
|
||||
sorting the table. The `createSearch` function can be applied here.
|
||||
- `headerRowProps: object` - Props to apply to the header row.
|
||||
- `datumRowProps: object | object => object` - Props to apply to the data
|
||||
rows. If a function is specified instead, the data associated with that row
|
||||
is passed into it.
|
||||
- `...rest` - The rest of the props are applied on the table.
|
||||
- `children: Component[]` - Not supported. Do not use.
|
||||
|
||||
The column types is defined as such:
|
||||
|
||||
```ts
|
||||
interface UnnamedColumn {
|
||||
// ? signifies optional
|
||||
datum?: {
|
||||
// Pass the children directly or pass a function which takes the value of
|
||||
// the cell and returns the props. Setting this property overrides the
|
||||
// default children.
|
||||
children?: React.ReactNode | ((value: object) => React.ReactNode);
|
||||
// Pass the additional props directly or pass a function which takes the
|
||||
// value of the cell and returns the additional props.
|
||||
props?: CellProps | ((value: object) => CellProps);
|
||||
};
|
||||
header?: {
|
||||
// Additional props for the header cell.
|
||||
props?: HeaderProps;
|
||||
};
|
||||
}
|
||||
|
||||
// Inherits properties from UnnamedColumn
|
||||
type Column = UnnamedColumn & {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
```
|
||||
|
||||
The following is an example of how to use `Table.Sortable`.
|
||||
|
||||
```jsx
|
||||
const data = [
|
||||
{ 'account_number': 6224001, 'name': 'Command Account', 'suspended': 'Active', 'money': 7000, },
|
||||
{ 'account_number': 3099002, 'name': 'Security Account', 'suspended': 'Active', 'money': 14000, },
|
||||
{ 'account_number': 8652003, 'name': 'Science Account', 'suspended': 'Active', 'money': 7000, },
|
||||
{ 'account_number': 8422004, 'name': 'Service Account', 'suspended': 'Active', 'money': 7000, },
|
||||
{ 'account_number': 9853005, 'name': 'Supply Account', 'suspended': 'Active', 'money': 7000, },
|
||||
{ 'account_number': 1866006, 'name': 'Engineering Account', 'suspended': 'Active', 'money': 7250, },
|
||||
{ 'account_number': 3811007, 'name': 'Medical Account', 'suspended': 'Active', 'money': 7000, },
|
||||
{ 'account_number': 3945008, 'name': 'Assistant Account', 'suspended': 'Active', 'money': 4500, },
|
||||
]
|
||||
|
||||
<Table.Sortable
|
||||
columns={[
|
||||
{ id: "name", name: "Department Name", },
|
||||
{ id: "account_number", name: "Account Number", },
|
||||
{ id: "suspended", name: "Account Status", },
|
||||
{ id: "money", name: "Account Balance", },
|
||||
]}
|
||||
data={data}
|
||||
datumID={(datum) => datum.account_number}
|
||||
|
||||
datumRowProps={(datum) => ({
|
||||
className: `AccountsUplinkTerminal__listRow--${datum.suspended}`,
|
||||
})}
|
||||
datumCellChildren={{
|
||||
name: (value) => <><Icon name="wallet" /> {value}</>,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
In the above example, the `columns` prop defines the fields in the data that
|
||||
are used. `columns.id` is the key or the name of the property on the data,
|
||||
while `columns.name` is what the user sees on the UI. `datumID` selects which
|
||||
field in the data to use as the key for render caching. `datumID` can return
|
||||
anything, but it must be unique.
|
||||
|
||||
`datumRowProps` applies a class to a row if the account associated with that
|
||||
row is suspended, and `datumCellChildren` prepends a wallet to the contents
|
||||
of the `name` column.
|
||||
|
||||
### `Tabs`
|
||||
|
||||
Tabs make it easy to explore and switch between different views.
|
||||
@@ -1068,19 +968,3 @@ Can be scrollable.
|
||||
- `className: string` - Applies a CSS class to the element.
|
||||
- `scrollable: boolean` - Shows or hides the scrollbar.
|
||||
- `children: any` - Main content of your window.
|
||||
|
||||
## `tgui/interfaces/common`
|
||||
|
||||
## `RecordsTable`
|
||||
|
||||
An extension to [`Table.Sortable`](#tablesortable) which provides a search
|
||||
function and slots for buttons.
|
||||
|
||||
**Props:**
|
||||
|
||||
- See inherited props: [`Table.Sortable`](#tablesortable)
|
||||
- `leftButtons: Component` - Optional buttons to add left of the search box.
|
||||
- `rightButtons: Component` - Optional buttons to add right of the search box.
|
||||
- `searchPlaceholder: string` - The default text in the search box.
|
||||
|
||||
Use a `<Fragment>`(or `<>`) to specify multiple buttons.
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { computeBoxClassName, computeBoxProps } from './Box';
|
||||
import { Component } from 'inferno';
|
||||
import { Button } from './Button';
|
||||
import { Icon } from './Icon';
|
||||
import { Box, computeBoxClassName, computeBoxProps } from './Box';
|
||||
|
||||
export const Table = (props) => {
|
||||
const { className, collapsing, children, ...rest } = props;
|
||||
@@ -56,159 +53,7 @@ export const TableCell = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const resolveFunctionalProp = (props, ...data) =>
|
||||
props ? (props instanceof Function ? props(...data) : props) : undefined;
|
||||
class HoverableIcon extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
hovering: false,
|
||||
};
|
||||
this.handleMouseOver = (e) => {
|
||||
this.setState({ hovering: true });
|
||||
};
|
||||
this.handleMouseOut = (e) => {
|
||||
this.setState({ hovering: false });
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hoverIcon, name, ...rest } = this.props;
|
||||
const { hovering } = this.state;
|
||||
return (
|
||||
<Icon
|
||||
name={hovering ? hoverIcon : name}
|
||||
{...rest}
|
||||
onMouseOver={this.handleMouseOver}
|
||||
onMouseOut={this.handleMouseOut}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SortableTable extends Component {
|
||||
constructor(props) {
|
||||
super();
|
||||
this.state = {
|
||||
// Allow null
|
||||
sortId: props.sortId === undefined ? props.columns[0].id : props.sortId,
|
||||
sortOrder: props.sortOrder ?? 1,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
columnDefaults,
|
||||
columns,
|
||||
data,
|
||||
datumID,
|
||||
filter,
|
||||
headerRowProps,
|
||||
datumRowProps,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { sortId, sortOrder } = this.state;
|
||||
|
||||
const columnHeaders = columns.map(
|
||||
({ id, name, header: { props } = {} }) => {
|
||||
const {
|
||||
header: { props: defaultProps },
|
||||
} = columnDefaults;
|
||||
return (
|
||||
<Table.Cell key={id}>
|
||||
<Button
|
||||
color={sortId !== id && 'transparent'}
|
||||
width="100%"
|
||||
onClick={() => {
|
||||
if (sortId === id) {
|
||||
this.setState({
|
||||
sortOrder: !sortOrder,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
sortId: id,
|
||||
sortOrder: true,
|
||||
});
|
||||
}
|
||||
}}
|
||||
{...defaultProps}
|
||||
{...props}
|
||||
>
|
||||
{name}
|
||||
{sortId === id && (
|
||||
<HoverableIcon
|
||||
name={sortOrder ? 'sort-up' : 'sort-down'}
|
||||
hoverIcon="times"
|
||||
position="absolute"
|
||||
right={0}
|
||||
top="50%"
|
||||
style={{
|
||||
transform: 'translate(0, -50%)',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
this.setState({
|
||||
sortId: null,
|
||||
});
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
);
|
||||
}
|
||||
);
|
||||
const dataRows = (filter ? filter(data) : data)
|
||||
.sort((a, b) => {
|
||||
if (sortId) {
|
||||
const i = sortOrder ? 1 : -1;
|
||||
return a[sortId].toString().localeCompare(b[sortId].toString()) * i;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
.map((datum) => {
|
||||
let cells = columns.map(
|
||||
({ id, name, datum: { props, children } = {} }) => {
|
||||
const {
|
||||
datum: { children: defaultChildren, props: defaultProps },
|
||||
} = columnDefaults;
|
||||
return (
|
||||
<Table.Cell
|
||||
key={id}
|
||||
{...(resolveFunctionalProp(defaultProps, datum[id]) ?? [])}
|
||||
{...(resolveFunctionalProp(props, datum[id]) ?? [])}
|
||||
>
|
||||
{resolveFunctionalProp(children, datum[id]) ??
|
||||
resolveFunctionalProp(defaultChildren, datum[id]) ??
|
||||
datum[id]}
|
||||
</Table.Cell>
|
||||
);
|
||||
}
|
||||
);
|
||||
return (
|
||||
<Table.Row
|
||||
key={datumID(datum)}
|
||||
{...(resolveFunctionalProp(datumRowProps, datum) ?? [])}
|
||||
>
|
||||
{cells}
|
||||
</Table.Row>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Table className={classes(['SortableTable', className])} {...rest}>
|
||||
<Table.Row bold {...headerRowProps}>
|
||||
{columnHeaders}
|
||||
</Table.Row>
|
||||
{dataRows}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableCell.defaultHooks = pureComponentHooks;
|
||||
|
||||
Table.Row = TableRow;
|
||||
Table.Cell = TableCell;
|
||||
Table.Sortable = SortableTable;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createSearch } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Icon,
|
||||
Input,
|
||||
LabeledList,
|
||||
@@ -9,10 +11,11 @@ import {
|
||||
Table,
|
||||
Tabs,
|
||||
} from '../components';
|
||||
import { FlexItem } from '../components/Flex';
|
||||
import { TableCell } from '../components/Table';
|
||||
import { Window } from '../layouts';
|
||||
import { LoginInfo } from './common/LoginInfo';
|
||||
import { LoginScreen } from './common/LoginScreen';
|
||||
import { RecordsTable } from './common/RecordsTable';
|
||||
|
||||
export const AccountsUplinkTerminal = (properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
@@ -39,7 +42,7 @@ export const AccountsUplinkTerminal = (properties, context) => {
|
||||
|
||||
return (
|
||||
<Window resizable>
|
||||
<Window.Content scrollable className="Layout__content--flexColumn">
|
||||
<Window.Content scrollable>
|
||||
<LoginInfo />
|
||||
<AccountsUplinkTerminalNavigation />
|
||||
{body}
|
||||
@@ -78,95 +81,159 @@ const AccountsUplinkTerminalContent = (props, context) => {
|
||||
}
|
||||
};
|
||||
|
||||
const AccountsRecordList = (props, context) => {
|
||||
const AccountsRecordList = (properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { accounts } = data;
|
||||
const [searchText, setSearchText] = useLocalState(context, 'searchText', '');
|
||||
const [sortId, _setSortId] = useLocalState(context, 'sortId', 'owner_name');
|
||||
const [sortOrder, _setSortOrder] = useLocalState(context, 'sortOrder', true);
|
||||
return (
|
||||
<RecordsTable
|
||||
columns={[
|
||||
{
|
||||
id: 'owner_name',
|
||||
name: 'Account Holder',
|
||||
datum: {
|
||||
children: (value) => (
|
||||
<>
|
||||
<Icon name="user" /> {value}
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'account_number',
|
||||
name: 'Account Number',
|
||||
datum: { children: (value) => <>#{value}</> },
|
||||
},
|
||||
{ id: 'suspended', name: 'Account Status' },
|
||||
{ id: 'money', name: 'Account Balance' },
|
||||
]}
|
||||
data={accounts}
|
||||
datumID={(datum) => datum.account_number}
|
||||
leftButtons={
|
||||
<Button
|
||||
content="New Account"
|
||||
icon="plus"
|
||||
onClick={() => act('create_new_account')}
|
||||
/>
|
||||
}
|
||||
searchPlaceholder="Search by account holder, number, status"
|
||||
datumRowProps={(datum) => ({
|
||||
className: `AccountsUplinkTerminal__listRow--${datum.suspended}`,
|
||||
onClick: () =>
|
||||
act('view_account_detail', {
|
||||
account_num: datum.account_number,
|
||||
}),
|
||||
})}
|
||||
/>
|
||||
<Flex direction="column" height="100%">
|
||||
<AccountsActions />
|
||||
<Flex.Item flexGrow="1" mt="0.5rem">
|
||||
<Section height="100%">
|
||||
<Table className="AccountsUplinkTerminal__list">
|
||||
<Table.Row bold>
|
||||
<SortButton id="owner_name">Account Holder</SortButton>
|
||||
<SortButton id="account_number">Account Number</SortButton>
|
||||
<SortButton id="suspended">Account Status</SortButton>
|
||||
<SortButton id="money">Account Balance</SortButton>
|
||||
</Table.Row>
|
||||
{accounts
|
||||
.filter(
|
||||
createSearch(searchText, (account) => {
|
||||
return (
|
||||
account.owner_name +
|
||||
'|' +
|
||||
account.account_number +
|
||||
'|' +
|
||||
account.suspended +
|
||||
'|' +
|
||||
account.money
|
||||
);
|
||||
})
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const i = sortOrder ? 1 : -1;
|
||||
return a[sortId].localeCompare(b[sortId]) * i;
|
||||
})
|
||||
.map((account) => (
|
||||
<Table.Row
|
||||
key={account.account_number}
|
||||
className={
|
||||
'AccountsUplinkTerminal__listRow--' + account.suspended
|
||||
}
|
||||
onClick={() =>
|
||||
act('view_account_detail', {
|
||||
account_num: account.account_number,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Table.Cell>
|
||||
<Icon name="user" /> {account.owner_name}
|
||||
</Table.Cell>
|
||||
<Table.Cell>#{account.account_number}</Table.Cell>
|
||||
<Table.Cell>{account.suspended}</Table.Cell>
|
||||
<Table.Cell>{account.money}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const DepartmentAccountsList = (props, context) => {
|
||||
const DepartmentAccountsList = (properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { department_accounts } = data;
|
||||
return (
|
||||
<RecordsTable
|
||||
columns={[
|
||||
{
|
||||
id: 'name',
|
||||
name: 'Department Name',
|
||||
datum: {
|
||||
children: (value) => (
|
||||
<>
|
||||
<Icon name="wallet" /> {value}
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'account_number',
|
||||
name: 'Account Number',
|
||||
datum: { children: (value) => <>#{value}</> },
|
||||
},
|
||||
{ id: 'suspended', name: 'Account Status' },
|
||||
{ id: 'money', name: 'Account Balance' },
|
||||
]}
|
||||
data={department_accounts}
|
||||
datumID={(datum) => datum.account_number}
|
||||
leftButtons={
|
||||
<Flex direction="column" height="100%">
|
||||
<AccountsActions />
|
||||
<Flex.Item flexGrow="1" mt="0.5rem">
|
||||
<Section height="100%">
|
||||
<Table className="AccountsUplinkTerminal__list">
|
||||
<Table.Row bold>
|
||||
<TableCell>Department Name</TableCell>
|
||||
<TableCell>Account Number</TableCell>
|
||||
<TableCell>Account Status</TableCell>
|
||||
<TableCell>Account Balance</TableCell>
|
||||
</Table.Row>
|
||||
{department_accounts.map((account) => (
|
||||
<Table.Row
|
||||
key={account.account_number}
|
||||
className={
|
||||
'AccountsUplinkTerminal__listRow--' + account.suspended
|
||||
}
|
||||
onClick={() =>
|
||||
act('view_account_detail', {
|
||||
account_num: account.account_number,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Table.Cell>
|
||||
<Icon name="wallet" /> {account.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell>#{account.account_number}</Table.Cell>
|
||||
<Table.Cell>{account.suspended}</Table.Cell>
|
||||
<Table.Cell>{account.money}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const SortButton = (properties, context) => {
|
||||
const [sortId, setSortId] = useLocalState(context, 'sortId', 'name');
|
||||
const [sortOrder, setSortOrder] = useLocalState(context, 'sortOrder', true);
|
||||
const { id, children } = properties;
|
||||
return (
|
||||
<Table.Cell>
|
||||
<Button
|
||||
color={sortId !== id && 'transparent'}
|
||||
width="100%"
|
||||
onClick={() => {
|
||||
if (sortId === id) {
|
||||
setSortOrder(!sortOrder);
|
||||
} else {
|
||||
setSortId(id);
|
||||
setSortOrder(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{sortId === id && (
|
||||
<Icon name={sortOrder ? 'sort-up' : 'sort-down'} ml="0.25rem;" />
|
||||
)}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
);
|
||||
};
|
||||
|
||||
const AccountsActions = (properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { is_printing } = data;
|
||||
const [searchText, setSearchText] = useLocalState(context, 'searchText', '');
|
||||
return (
|
||||
<Flex>
|
||||
<FlexItem>
|
||||
<Button
|
||||
content="New Account"
|
||||
icon="plus"
|
||||
onClick={() => act('create_new_account')}
|
||||
/>
|
||||
}
|
||||
searchPlaceholder="Search by department name, account number, status, and balance"
|
||||
datumRowProps={(datum) => ({
|
||||
className: `AccountsUplinkTerminal__listRow--${datum.suspended}`,
|
||||
onClick: () =>
|
||||
act('view_account_detail', {
|
||||
account_num: datum.account_number,
|
||||
}),
|
||||
})}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem grow="1" ml="0.5rem">
|
||||
<Input
|
||||
placeholder="Search by account holder, number, status"
|
||||
width="100%"
|
||||
onInput={(e, value) => setSearchText(value)}
|
||||
/>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { createSearch } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Icon,
|
||||
Input,
|
||||
LabeledList,
|
||||
Section,
|
||||
Tabs,
|
||||
@@ -15,11 +17,11 @@ import {
|
||||
modalOpen,
|
||||
modalRegisterBodyOverride,
|
||||
} from '../interfaces/common/ComplexModal';
|
||||
import { FlexItem } from '../components/Flex';
|
||||
import { Window } from '../layouts';
|
||||
import { LoginInfo } from './common/LoginInfo';
|
||||
import { LoginScreen } from './common/LoginScreen';
|
||||
import { TemporaryNotice } from './common/TemporaryNotice';
|
||||
import { RecordsTable } from './common/RecordsTable';
|
||||
|
||||
const severities = {
|
||||
'Minor': 'lightgray',
|
||||
@@ -114,43 +116,80 @@ export const MedicalRecords = (_properties, context) => {
|
||||
);
|
||||
};
|
||||
|
||||
const MedicalRecordsList = (props, context) => {
|
||||
const MedicalRecordsList = (_properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { records } = data;
|
||||
const [searchText, setSearchText] = useLocalState(context, 'searchText', '');
|
||||
const [sortId, _setSortId] = useLocalState(context, 'sortId', 'name');
|
||||
const [sortOrder, _setSortOrder] = useLocalState(context, 'sortOrder', true);
|
||||
return (
|
||||
<RecordsTable
|
||||
columns={[
|
||||
{
|
||||
id: 'name',
|
||||
name: 'Name',
|
||||
datum: {
|
||||
children: (value) => (
|
||||
<>
|
||||
<Icon name="user" /> {value}
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
{ id: 'id', name: 'ID' },
|
||||
{ id: 'rank', name: 'Assignment' },
|
||||
{ id: 'p_stat', name: 'Patient Status' },
|
||||
{ id: 'm_stat', name: 'Mental Status' },
|
||||
]}
|
||||
data={records}
|
||||
datumID={(datum) => datum.ref}
|
||||
leftButtons={
|
||||
<Button
|
||||
content="Manage Records"
|
||||
icon="wrench"
|
||||
onClick={() => act('screen', { screen: 3 })}
|
||||
/>
|
||||
}
|
||||
searchPlaceholder="Search by Name, ID, Physical Status, or Mental Status"
|
||||
datumRowProps={(datum) => ({
|
||||
className: `MedicalRecords__listRow--${medStatusStyles[datum.p_stat]}`,
|
||||
onClick: () => act('view_record', { view_record: datum.ref }),
|
||||
})}
|
||||
/>
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex>
|
||||
<FlexItem>
|
||||
<Button
|
||||
content="Manage Records"
|
||||
icon="wrench"
|
||||
ml="0.25rem"
|
||||
onClick={() => act('screen', { screen: 3 })}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem grow="1" ml="0.5rem">
|
||||
<Input
|
||||
placeholder="Search by Name, ID, Physical Status, or Mental Status"
|
||||
width="100%"
|
||||
onInput={(e, value) => setSearchText(value)}
|
||||
/>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
<Section flexGrow="1" mt="0.5rem">
|
||||
<Table className="MedicalRecords__list">
|
||||
<Table.Row bold>
|
||||
<SortButton id="name">Name</SortButton>
|
||||
<SortButton id="id">ID</SortButton>
|
||||
<SortButton id="rank">Assignment</SortButton>
|
||||
<SortButton id="p_stat">Patient Status</SortButton>
|
||||
<SortButton id="m_stat">Mental Status</SortButton>
|
||||
</Table.Row>
|
||||
{records
|
||||
.filter(
|
||||
createSearch(searchText, (record) => {
|
||||
return (
|
||||
record.name +
|
||||
'|' +
|
||||
record.id +
|
||||
'|' +
|
||||
record.rank +
|
||||
'|' +
|
||||
record.p_stat +
|
||||
'|' +
|
||||
record.m_stat
|
||||
);
|
||||
})
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const i = sortOrder ? 1 : -1;
|
||||
return a[sortId].localeCompare(b[sortId]) * i;
|
||||
})
|
||||
.map((record) => (
|
||||
<Table.Row
|
||||
key={record.id}
|
||||
className={
|
||||
'MedicalRecords__listRow--' + medStatusStyles[record.p_stat]
|
||||
}
|
||||
onClick={() => act('view_record', { view_record: record.ref })}
|
||||
>
|
||||
<Table.Cell>
|
||||
<Icon name="user" /> {record.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{record.id}</Table.Cell>
|
||||
<Table.Cell>{record.rank}</Table.Cell>
|
||||
<Table.Cell>{record.p_stat}</Table.Cell>
|
||||
<Table.Cell>{record.m_stat}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -334,42 +373,62 @@ const MedicalRecordsViewMedical = (_properties, context) => {
|
||||
);
|
||||
};
|
||||
|
||||
const MedicalRecordsViruses = (props, context) => {
|
||||
const MedicalRecordsViruses = (_properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { virus } = data;
|
||||
const [searchText, setSearchText] = useLocalState(context, 'searchText', '');
|
||||
const [sortId2, _setSortId2] = useLocalState(context, 'sortId2', 'name');
|
||||
const [sortOrder2, _setSortOrder2] = useLocalState(
|
||||
context,
|
||||
'sortOrder2',
|
||||
true
|
||||
);
|
||||
return (
|
||||
<RecordsTable
|
||||
columns={[
|
||||
{
|
||||
id: 'name',
|
||||
name: 'Name',
|
||||
datum: {
|
||||
children: (value) => (
|
||||
<>
|
||||
<Icon name="virus" /> {value}
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
{ id: 'max_stages', name: 'Max Stages' },
|
||||
{
|
||||
id: 'severity',
|
||||
name: 'Severity',
|
||||
datum: {
|
||||
props: (value) => ({
|
||||
color: severities[value],
|
||||
}),
|
||||
},
|
||||
},
|
||||
]}
|
||||
data={virus}
|
||||
datumID={(datum) => datum.id}
|
||||
searchPlaceholder="Search by Name, Max Stages, or Severity"
|
||||
datumRowProps={(datum) => ({
|
||||
className: `MedicalRecords__listVirus--${datum.severity}`,
|
||||
onClick: () => act('vir', { vir: datum.D }),
|
||||
})}
|
||||
/>
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex>
|
||||
<FlexItem grow="1" ml="0.5rem">
|
||||
<Input
|
||||
placeholder="Search by Name, Max Stages, or Severity"
|
||||
width="100%"
|
||||
onInput={(e, value) => setSearchText(value)}
|
||||
/>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
<Section flexGrow="1" mt="0.5rem">
|
||||
<Table className="MedicalRecords__list">
|
||||
<Table.Row bold>
|
||||
<SortButton2 id="name">Name</SortButton2>
|
||||
<SortButton2 id="max_stages">Max Stages</SortButton2>
|
||||
<SortButton2 id="severity">Severity</SortButton2>
|
||||
</Table.Row>
|
||||
{virus
|
||||
.filter(
|
||||
createSearch(searchText, (vir) => {
|
||||
return vir.name + '|' + vir.max_stages + '|' + vir.severity;
|
||||
})
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const i = sortOrder2 ? 1 : -1;
|
||||
return a[sortId2].localeCompare(b[sortId2]) * i;
|
||||
})
|
||||
.map((vir) => (
|
||||
<Table.Row
|
||||
key={vir.id}
|
||||
className={'MedicalRecords__listVirus--' + vir.severity}
|
||||
onClick={() => act('vir', { vir: vir.D })}
|
||||
>
|
||||
<Table.Cell>
|
||||
<Icon name="virus" /> {vir.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{vir.max_stages}</Table.Cell>
|
||||
<Table.Cell color={severities[vir.severity]}>
|
||||
{vir.severity}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -423,6 +482,64 @@ const MedicalRecordsMedbots = (_properties, context) => {
|
||||
);
|
||||
};
|
||||
|
||||
const SortButton = (properties, context) => {
|
||||
const [sortId, setSortId] = useLocalState(context, 'sortId', 'name');
|
||||
const [sortOrder, setSortOrder] = useLocalState(context, 'sortOrder', true);
|
||||
const { id, children } = properties;
|
||||
return (
|
||||
<Table.Cell>
|
||||
<Button
|
||||
color={sortId !== id && 'transparent'}
|
||||
width="100%"
|
||||
onClick={() => {
|
||||
if (sortId === id) {
|
||||
setSortOrder(!sortOrder);
|
||||
} else {
|
||||
setSortId(id);
|
||||
setSortOrder(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{sortId === id && (
|
||||
<Icon name={sortOrder ? 'sort-up' : 'sort-down'} ml="0.25rem;" />
|
||||
)}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
);
|
||||
};
|
||||
|
||||
const SortButton2 = (properties, context) => {
|
||||
const [sortId2, setSortId2] = useLocalState(context, 'sortId2', 'name');
|
||||
const [sortOrder2, setSortOrder2] = useLocalState(
|
||||
context,
|
||||
'sortOrder2',
|
||||
true
|
||||
);
|
||||
const { id, children } = properties;
|
||||
return (
|
||||
<Table.Cell>
|
||||
<Button
|
||||
color={sortId2 !== id && 'transparent'}
|
||||
width="100%"
|
||||
onClick={() => {
|
||||
if (sortId2 === id) {
|
||||
setSortOrder2(!sortOrder2);
|
||||
} else {
|
||||
setSortId2(id);
|
||||
setSortOrder2(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{sortId2 === id && (
|
||||
<Icon name={sortOrder2 ? 'sort-up' : 'sort-down'} ml="0.25rem;" />
|
||||
)}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
);
|
||||
};
|
||||
|
||||
const MedicalRecordsNavigation = (_properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { screen, general } = data;
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
import { decodeHtmlEntities } from 'common/string';
|
||||
import { createSearch, decodeHtmlEntities } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Icon, LabeledList, Section, Tabs } from '../components';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Icon,
|
||||
Input,
|
||||
LabeledList,
|
||||
Section,
|
||||
Table,
|
||||
Tabs,
|
||||
} from '../components';
|
||||
import { FlexItem } from '../components/Flex';
|
||||
import { Window } from '../layouts';
|
||||
import { ComplexModal, modalOpen } from './common/ComplexModal';
|
||||
import { LoginInfo } from './common/LoginInfo';
|
||||
import { LoginScreen } from './common/LoginScreen';
|
||||
import { TemporaryNotice } from './common/TemporaryNotice';
|
||||
import { RecordsTable } from './common/RecordsTable';
|
||||
|
||||
const statusStyles = {
|
||||
'*Execute*': 'execute',
|
||||
@@ -85,56 +95,128 @@ const SecurityRecordsNavigation = (properties, context) => {
|
||||
);
|
||||
};
|
||||
|
||||
const SecurityRecordsPageList = (props, context) => {
|
||||
const SecurityRecordsPageList = (properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { isPrinting, records } = data;
|
||||
const { records } = data;
|
||||
const [searchText, setSearchText] = useLocalState(context, 'searchText', '');
|
||||
const [sortId, _setSortId] = useLocalState(context, 'sortId', 'name');
|
||||
const [sortOrder, _setSortOrder] = useLocalState(context, 'sortOrder', true);
|
||||
return (
|
||||
<RecordsTable
|
||||
columns={[
|
||||
{
|
||||
id: 'name',
|
||||
name: 'Name',
|
||||
datum: {
|
||||
children: (value) => (
|
||||
<>
|
||||
<Icon name="user" /> {value}
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
{ id: 'id', name: 'ID' },
|
||||
{ id: 'rank', name: 'Assignment' },
|
||||
{ id: 'fingerprint', name: 'Fingerprint' },
|
||||
{ id: 'status', name: 'Criminal Status' },
|
||||
]}
|
||||
data={records}
|
||||
datumID={(datum) => datum.id}
|
||||
leftButtons={
|
||||
<>
|
||||
<Button
|
||||
content="New Record"
|
||||
icon="plus"
|
||||
onClick={() => act('new_general')}
|
||||
/>
|
||||
<Button
|
||||
disabled={isPrinting}
|
||||
icon={isPrinting ? 'spinner' : 'print'}
|
||||
iconSpin={!!isPrinting}
|
||||
content="Print Cell Log"
|
||||
onClick={() => modalOpen(context, 'print_cell_log')}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
searchPlaceholder="Search by Name, ID, Assignment, Fingerprint, Status"
|
||||
datumRowProps={(datum) => ({
|
||||
className: `SecurityRecords__listRow--${statusStyles[datum.status]}`,
|
||||
onClick: () =>
|
||||
act('view', {
|
||||
uid_gen: datum.uid_gen,
|
||||
uid_sec: datum.uid_sec,
|
||||
}),
|
||||
})}
|
||||
/>
|
||||
<Flex direction="column" height="100%">
|
||||
<SecurityRecordsActions />
|
||||
<Section flexGrow="1" mt="0.5rem">
|
||||
<Table className="SecurityRecords__list">
|
||||
<Table.Row bold>
|
||||
<SortButton id="name">Name</SortButton>
|
||||
<SortButton id="id">ID</SortButton>
|
||||
<SortButton id="rank">Assignment</SortButton>
|
||||
<SortButton id="fingerprint">Fingerprint</SortButton>
|
||||
<SortButton id="status">Criminal Status</SortButton>
|
||||
</Table.Row>
|
||||
{records
|
||||
.filter(
|
||||
createSearch(searchText, (record) => {
|
||||
return (
|
||||
record.name +
|
||||
'|' +
|
||||
record.id +
|
||||
'|' +
|
||||
record.rank +
|
||||
'|' +
|
||||
record.fingerprint +
|
||||
'|' +
|
||||
record.status
|
||||
);
|
||||
})
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const i = sortOrder ? 1 : -1;
|
||||
return a[sortId].localeCompare(b[sortId]) * i;
|
||||
})
|
||||
.map((record) => (
|
||||
<Table.Row
|
||||
key={record.id}
|
||||
className={
|
||||
'SecurityRecords__listRow--' + statusStyles[record.status]
|
||||
}
|
||||
onClick={() =>
|
||||
act('view', {
|
||||
uid_gen: record.uid_gen,
|
||||
uid_sec: record.uid_sec,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Table.Cell>
|
||||
<Icon name="user" /> {record.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{record.id}</Table.Cell>
|
||||
<Table.Cell>{record.rank}</Table.Cell>
|
||||
<Table.Cell>{record.fingerprint}</Table.Cell>
|
||||
<Table.Cell>{record.status}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const SortButton = (properties, context) => {
|
||||
const [sortId, setSortId] = useLocalState(context, 'sortId', 'name');
|
||||
const [sortOrder, setSortOrder] = useLocalState(context, 'sortOrder', true);
|
||||
const { id, children } = properties;
|
||||
return (
|
||||
<Table.Cell>
|
||||
<Button
|
||||
color={sortId !== id && 'transparent'}
|
||||
width="100%"
|
||||
onClick={() => {
|
||||
if (sortId === id) {
|
||||
setSortOrder(!sortOrder);
|
||||
} else {
|
||||
setSortId(id);
|
||||
setSortOrder(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{sortId === id && (
|
||||
<Icon name={sortOrder ? 'sort-up' : 'sort-down'} ml="0.25rem;" />
|
||||
)}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
);
|
||||
};
|
||||
|
||||
const SecurityRecordsActions = (properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { isPrinting } = data;
|
||||
const [searchText, setSearchText] = useLocalState(context, 'searchText', '');
|
||||
return (
|
||||
<Flex>
|
||||
<FlexItem>
|
||||
<Button
|
||||
content="New Record"
|
||||
icon="plus"
|
||||
onClick={() => act('new_general')}
|
||||
/>
|
||||
<Button
|
||||
disabled={isPrinting}
|
||||
icon={isPrinting ? 'spinner' : 'print'}
|
||||
iconSpin={!!isPrinting}
|
||||
content="Print Cell Log"
|
||||
ml="0.25rem"
|
||||
onClick={() => modalOpen(context, 'print_cell_log')}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem grow="1" ml="0.5rem">
|
||||
<Input
|
||||
placeholder="Search by Name, ID, Assignment, Fingerprint, Status"
|
||||
width="100%"
|
||||
onInput={(e, value) => setSearchText(value)}
|
||||
/>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Component } from 'inferno';
|
||||
import { Flex, Input, Section, Table } from '../../components';
|
||||
import { FlexItem } from '../../components/Flex';
|
||||
import { createSearch } from 'common/string';
|
||||
import { classes } from 'common/react';
|
||||
|
||||
export class RecordsTable extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
searchText: '',
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
columns,
|
||||
leftButtons,
|
||||
rightButtons,
|
||||
searchPlaceholder,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { searchText } = this.state;
|
||||
return (
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex className="RecordsTable_Toolbar">
|
||||
{leftButtons}
|
||||
<FlexItem grow="1">
|
||||
<Input
|
||||
placeholder={searchPlaceholder}
|
||||
width="100%"
|
||||
onInput={(e, value) => this.setState({ searchText: value })}
|
||||
/>
|
||||
</FlexItem>
|
||||
{rightButtons}
|
||||
</Flex>
|
||||
<Section flexGrow="1" mt="0.5rem">
|
||||
<Table.Sortable
|
||||
className={classes(['RecordsTable', className])}
|
||||
columns={columns}
|
||||
filter={(data) =>
|
||||
data.filter(
|
||||
createSearch(searchText, (datum) =>
|
||||
columns.map((c) => datum[c.id]).join('|')
|
||||
)
|
||||
)
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
</Section>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,6 +2,24 @@
|
||||
@use '../colors.scss';
|
||||
@use '../functions.scss' as *;
|
||||
|
||||
.AccountsUplinkTerminal__list {
|
||||
tr > td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
tr:not(:first-child) {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
|
||||
transition: background-color 50ms;
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba(base.$color-bg, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.AccountsUplinkTerminal__listRow--SUSPENDED {
|
||||
background-color: colors.bg(#890e26);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,24 @@
|
||||
@use '../colors.scss';
|
||||
@use '../functions.scss' as *;
|
||||
|
||||
.MedicalRecords__list {
|
||||
tr > td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
tr:not(:first-child) {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
|
||||
transition: background-color 50ms;
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba(base.$color-bg, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.MedicalRecords__listRow--deceased {
|
||||
background-color: colors.bg(#890e26);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,24 @@
|
||||
@use '../colors.scss';
|
||||
@use '../functions.scss' as *;
|
||||
|
||||
.SecurityRecords__list {
|
||||
tr > td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
tr:not(:first-child) {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
|
||||
transition: background-color 50ms;
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba(base.$color-bg, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.SecurityRecords__listRow--arrest {
|
||||
background-color: colors.bg(#890e26);
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
@use '../../base.scss';
|
||||
@use '../../colors.scss';
|
||||
@use '../../functions.scss' as *;
|
||||
|
||||
.RecordsTable {
|
||||
tr > td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
tr:not(:first-child) {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
|
||||
transition: background-color 50ms;
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba(base.$color-bg, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.RecordsTable_Toolbar {
|
||||
> {
|
||||
*:not(:first-child) {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@
|
||||
@include meta.load-css('./components/Tooltip.scss');
|
||||
|
||||
// Interfaces
|
||||
@include meta.load-css('./interfaces/common/RecordsTable.scss');
|
||||
@include meta.load-css('./interfaces/AccountsUplinkTerminal.scss');
|
||||
@include meta.load-css('./interfaces/BrigCells.scss');
|
||||
@include meta.load-css('./interfaces/CameraConsole.scss');
|
||||
|
||||
Reference in New Issue
Block a user