navbar.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { Menubar } from 'primereact/menubar';
  2. import React, { useContext, useEffect, useRef, useState } from 'react';
  3. import { useNavigate } from 'react-router-dom';
  4. import { AuthContext, TimeContext } from '../contexts';
  5. import { Configuration } from '../services';
  6. import { Airport } from '../services';
  7. import { AirportOverview, BackendReturnStatus, IAuthState } from '../types';
  8. export const NavBar: React.FC = () => {
  9. const [timestamp, setTimestamp] = useState<string>('');
  10. const [fullName, setFullName] = useState<string>('');
  11. const [menuTree, setMenuTree] = useState<any>(undefined);
  12. const authContext = useContext(AuthContext);
  13. const timeContext = useContext(TimeContext);
  14. const currentAuth = useRef<IAuthState>();
  15. const navigate = useNavigate();
  16. currentAuth.current = authContext.auth;
  17. const firBasedSubMenu = (airports: AirportOverview[], endpoint: string): any[] => {
  18. if (airports.length === 0) {
  19. return [{
  20. label: 'No airport available',
  21. disabled: true,
  22. }];
  23. }
  24. const firAirports: [string, AirportOverview[]][] = [];
  25. // cluster airports based on FIRs
  26. airports.forEach((airport) => {
  27. const idx = firAirports.findIndex((value) => value[0] === airport.flightInformationRegion);
  28. if (idx !== -1) {
  29. firAirports[idx][1].push(airport);
  30. } else {
  31. firAirports.push([airport.flightInformationRegion, [airport]]);
  32. }
  33. });
  34. // create the submenu for every FIR
  35. const retval: any[] = [];
  36. firAirports.forEach((fir) => {
  37. retval.push({
  38. label: fir[0],
  39. items: [],
  40. });
  41. // sort airports alphabetically
  42. fir[1].sort();
  43. // create the airport with the link
  44. fir[1].forEach((airport) => {
  45. retval[retval.length - 1].push({
  46. label: `${airport.icao} - ${airport.name}`,
  47. command: () => navigate(`${endpoint}?icao=${airport.icao}`),
  48. });
  49. });
  50. });
  51. return retval;
  52. }
  53. const updateMenuItems = async () => {
  54. if (currentAuth.current === undefined || !currentAuth.current.valid) return [];
  55. Airport.all().then((response) => {
  56. if (response.status !== BackendReturnStatus.Ok) return [];
  57. const newMenuTree: { label: string; items?: any[]; command?: () => void }[] = [
  58. {
  59. label: 'Airports',
  60. items: [] as any[],
  61. }
  62. ];
  63. // create the airports subtree
  64. const airportSubtree = firBasedSubMenu(response.airports, '/sequence');
  65. newMenuTree[0].items = airportSubtree;
  66. // collect all configuration airports
  67. const configurationAirports: AirportOverview[] = [];
  68. response.airports.forEach((airport) => {
  69. const idx = currentAuth.current?.user.airportConfigurationAccess.findIndex((value) => airport.icao === value);
  70. if (idx !== -1) configurationAirports.push(airport);
  71. });
  72. // create the configuration subtree
  73. if (configurationAirports.length !== 0) {
  74. const configurationSubtree = firBasedSubMenu(configurationAirports, '/configure/airport');
  75. newMenuTree[1].items = configurationSubtree;
  76. }
  77. if (currentAuth.current?.user.administrator) {
  78. newMenuTree.push({
  79. label: 'Administration',
  80. items: [
  81. {
  82. label: 'Users',
  83. command: () => navigate('/admin/users'),
  84. },
  85. {
  86. label: 'Airports',
  87. command: () => navigate('/admin/airports'),
  88. },
  89. ],
  90. });
  91. }
  92. newMenuTree.push({
  93. label: 'Logout',
  94. command: () => navigate('/logout'),
  95. });
  96. setMenuTree(newMenuTree);
  97. });
  98. }
  99. useEffect(() => {
  100. const event = new EventSource(`${Configuration.resourceServer}/airport/renew`);
  101. event.onmessage = () => {
  102. updateMenuItems();
  103. }
  104. const timeInterval = setInterval(() => {
  105. const serverUtcTime = new Date(new Date().getTime() + timeContext.offset);
  106. const hours = String(serverUtcTime.getUTCHours()).padStart(2, '0');
  107. const minutes = String(serverUtcTime.getUTCMinutes()).padStart(2, '0');
  108. const seconds = String(serverUtcTime.getUTCSeconds()).padStart(2, '0');
  109. if (currentAuth.current?.valid) {
  110. setTimestamp(`${hours}:${minutes}:${seconds}`);
  111. }
  112. }, 1000);
  113. return () => {
  114. clearInterval(timeInterval);
  115. event.close();
  116. }
  117. // eslint-disable-next-line react-hooks/exhaustive-deps
  118. }, []);
  119. useEffect(() => {
  120. if (currentAuth.current?.valid) {
  121. if (currentAuth.current.user.fullName !== '') {
  122. setFullName(currentAuth.current.user.fullName);
  123. } else {
  124. setFullName(currentAuth.current.user.vatsimId);
  125. }
  126. updateMenuItems();
  127. } else {
  128. setFullName('');
  129. setMenuTree([]);
  130. }
  131. // eslint-disable-next-line react-hooks/exhaustive-deps
  132. }, [authContext]);
  133. if (menuTree === undefined || !currentAuth.current.valid) return <></>;
  134. const rightSideInfo = (
  135. <div>{fullName} | {timestamp}</div>
  136. );
  137. return (menuTree.length !== 0 ? <Menubar model={menuTree} end={rightSideInfo} /> : <></>);
  138. };