diff --git a/tgui/packages/tgui/interfaces/CrewManifest.jsx b/tgui/packages/tgui/interfaces/CrewManifest.jsx new file mode 100644 index 00000000000..7b276dbd1f9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CrewManifest.jsx @@ -0,0 +1,98 @@ +import { classes } from 'common/react'; + +import { useBackend } from '../backend'; +import { Icon, Section, Table, Tooltip } from '../components'; +import { Window } from '../layouts'; + +const commandJobs = [ + 'Head of Personnel', + 'Head of Security', + 'Chief Engineer', + 'Research Director', + 'Chief Medical Officer', +]; + +export const CrewManifest = (props) => { + const { + data: { manifest, positions }, + } = useBackend(); + + return ( + + + {Object.entries(manifest).map(([dept, crew]) => ( +
+ + {Object.entries(crew).map(([crewIndex, crewMember]) => ( + + + {crewMember.name} + + + {positions[dept].exceptions.includes(crewMember.rank) && ( + + + + )} + {crewMember.rank === 'Captain' && ( + + + + )} + {commandJobs.includes(crewMember.rank) && ( + + + + )} + + + {crewMember.rank} + + + ))} +
+
+ ))} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/LanguageMenu.jsx b/tgui/packages/tgui/interfaces/LanguageMenu.jsx new file mode 100644 index 00000000000..fc5a43feb61 --- /dev/null +++ b/tgui/packages/tgui/interfaces/LanguageMenu.jsx @@ -0,0 +1,111 @@ +import { useBackend } from '../backend'; +import { Button, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; + +export const LanguageMenu = (props) => { + const { act, data } = useBackend(); + const { + admin_mode, + is_living, + omnitongue, + languages = [], + unknown_languages = [], + } = data; + return ( + + +
+ + {languages.map((language) => ( + + {!!is_living && ( +
+ {!!admin_mode && ( +
act('toggle_omnitongue')} + /> + } + > + + {unknown_languages.map((language) => ( + + act('grant_language', { + language_name: language.name, + }) + } + /> + } + > + {language.desc} Key: ,{language.key}{' '} + {!!language.shadow && '(gained from mob)'}{' '} + {language.can_speak ? 'Can speak.' : 'Cannot speak.'} + + ))} + +
+ )} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/TrackedPlaytime.jsx b/tgui/packages/tgui/interfaces/TrackedPlaytime.jsx new file mode 100644 index 00000000000..55eae8d2558 --- /dev/null +++ b/tgui/packages/tgui/interfaces/TrackedPlaytime.jsx @@ -0,0 +1,115 @@ +import { sortBy } from 'common/collections'; + +import { useBackend } from '../backend'; +import { Box, Button, Flex, ProgressBar, Section, Table } from '../components'; +import { Window } from '../layouts'; + +const JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED = 1; +const JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS = 2; + +const sortByPlaytime = sortBy(([_, playtime]) => -playtime); + +const PlaytimeSection = (props) => { + const { playtimes } = props; + + const sortedPlaytimes = sortByPlaytime(Object.entries(playtimes)).filter( + (entry) => entry[1], + ); + + if (!sortedPlaytimes.length) { + return 'No recorded playtime hours for this section.'; + } + + const mostPlayed = sortedPlaytimes[0][1]; + return ( + + {sortedPlaytimes.map(([jobName, playtime]) => { + const ratio = playtime / mostPlayed; + return ( + + + {jobName} + + + + + + + {(playtime / 60).toLocaleString(undefined, { + minimumFractionDigits: 1, + maximumFractionDigits: 1, + })} + h + + + + + + ); + })} +
+ ); +}; + +export const TrackedPlaytime = (props) => { + const { act, data } = useBackend(); + const { + failReason, + jobPlaytimes, + specialPlaytimes, + exemptStatus, + isAdmin, + livingTime, + ghostTime, + adminTime, + } = data; + return ( + + + {(failReason && + ((failReason === JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED && ( + This server has disabled tracking. + )) || + (failReason === JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS && ( + You have no records. + )))) || ( + +
+ +
+
act('toggle_exempt')} + > + Job Playtime Exempt + + ) + } + > + +
+
+ +
+
+ )} +
+
+ ); +};