initial commit

This commit is contained in:
2025-06-09 07:02:26 +02:00
commit 0297cfb600
67 changed files with 18344 additions and 0 deletions
@@ -0,0 +1,79 @@
import { FC } from 'react';
import { SharedConfig } from '../../configs/shared';
interface CGSelectProps {
value: number;
disabled: boolean;
increase: () => void;
decrease: () => void;
}
const CGSelect: FC<CGSelectProps> = ({ value, disabled, increase, decrease }) => {
return (
<div className="relative">
<input
disabled
className="w-full rounded-lg border border-white bg-zinc-700 px-3 py-2 text-white shadow-sm focus:border-blue-600 focus:outline-none focus:ring-blue-600"
value={value.toFixed(1)}
/>
<button
disabled={disabled || value <= SharedConfig.CGLimits.min}
className="absolute right-2 top-0 -mt-[.5px] border-t bg-zinc-700 text-white disabled:text-zinc-400"
onClick={increase}
>
{/* FIXME: FONTAWESOME IN EFB */}
<svg
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="caret-up"
className="svg-inline--fa fa-caret-up "
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
style={{
height: '1em',
verticalAlign: '-0.125em',
display: 'inline-block',
boxSizing: 'content-box',
}}
>
<path
fill="currentColor"
d="M182.6 137.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8H288c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z"
></path>
</svg>
</button>
<button
disabled={disabled || value >= SharedConfig.CGLimits.max}
className="absolute bottom-0 right-2 -mt-[.5px] border-b bg-zinc-700 text-white disabled:text-zinc-400"
onClick={decrease}
>
{/* FIXME: FONTAWESOME IN EFB */}
<svg
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="caret-down"
className="svg-inline--fa fa-caret-down "
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
style={{
height: '1em',
verticalAlign: '-0.125em',
display: 'inline-block',
boxSizing: 'content-box',
}}
>
<path
fill="currentColor"
d="M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z"
></path>
</svg>
</button>
</div>
);
};
export default CGSelect;
@@ -0,0 +1,294 @@
import { FC, useEffect, useState } from 'react';
import { PaxConfig, PayloadPax } from '../../configs/pax';
import { Fuel, SharedConfig } from '../../configs/shared';
import { ImportFlightPlan } from '../../utils/TFDISBImport';
import CGSelect from '../CGSelect/CGSelect';
import ActionBar from '../actionbar/ActionBar';
interface StationEntryProps {
unit: 'kg' | 'lbs';
isER: boolean;
initialPayload: PayloadPax;
fuelLive: Fuel;
payloadLive: PayloadPax;
loadingState: 'preview' | 'accepted' | 'loaded';
username: string;
setLoadingState: (newState: StationEntryProps['loadingState']) => void;
updateView: (payload: PayloadPax) => void;
loadAircraft: () => void;
}
const SBEntryPax: FC<StationEntryProps> = ({
unit,
isER,
initialPayload,
fuelLive,
payloadLive,
loadingState,
username,
setLoadingState,
updateView,
loadAircraft,
}) => {
const [targetZFWCG, setTargetZFWCG] = useState(SharedConfig.CGLimits.default);
const [fuel, setFuel] = useState(
Math.round(
fuelLive.main1 +
fuelLive.main1Tip +
fuelLive.main2 +
fuelLive.main3 +
fuelLive.main3Tip +
fuelLive.upperAux +
fuelLive.lowerAux +
fuelLive.tail +
fuelLive.forwardAux1 +
fuelLive.forwardAux2
)
);
const [ZFW, setZFW] = useState(
Math.round(
PaxConfig.weights.base[unit].total +
(isER ? SharedConfig.erExtraWeight[unit] * 2 : 0) +
payloadLive.empty +
initialPayload.business1Left +
initialPayload.business1Center +
initialPayload.business1Right +
initialPayload.business2Left +
initialPayload.business2Center +
initialPayload.business2Right +
initialPayload.economy1Left +
initialPayload.economy1Center +
initialPayload.economy1Right +
initialPayload.economy2Left +
initialPayload.economy2Center +
initialPayload.economy2Right +
initialPayload.forwardCargo +
initialPayload.rearCargo
)
);
const [SBPlan, setSBPlan] = useState<any>();
const [SBInFlight, setSBInFlight] = useState(false);
const _ZFW = () => {
if (loadingState !== 'loaded') return ZFW;
return Math.round(
payloadLive.empty +
payloadLive.pilot +
payloadLive.firstOfficer +
payloadLive.engineer +
payloadLive.cabinCrewFront +
payloadLive.business1Left +
payloadLive.business1Center +
payloadLive.business1Right +
payloadLive.business2Left +
payloadLive.business2Center +
payloadLive.business2Right +
payloadLive.economy1Left +
payloadLive.economy1Center +
payloadLive.economy1Right +
payloadLive.economy2Left +
payloadLive.economy2Center +
payloadLive.economy2Right +
payloadLive.cabinCrewRear +
payloadLive.forwardCargo +
payloadLive.rearCargo +
payloadLive.leftAuxPax +
payloadLive.rightAuxPax
);
};
const ZFWValid = () => {
return _ZFW() <= PaxConfig.maxZWF[unit];
};
const GW = () => {
return fuel + _ZFW();
};
const GWValid = () => {
return GW() <= (isER ? SharedConfig.maxTOW.er[unit] : SharedConfig.maxTOW.norm[unit]);
};
const handleInput = (input: string, maxValue: number, setter: (value: number) => void) => {
if (!input) {
setter(0);
return;
}
const converted = parseInt(input);
if (converted) {
if (converted < 0) setter(0);
else if (converted > maxValue) setter(maxValue);
else setter(converted);
}
};
const handleSB = async () => {
setSBInFlight(true);
const SBResponse = await ImportFlightPlan(username, PaxConfig, unit, isER);
if (SBResponse.type === 'error') {
console.error('TODO: ERROR', SBResponse.message);
setSBInFlight(false);
return;
}
const __ZFW = Math.round(
PaxConfig.weights.base[unit].total +
(isER ? SharedConfig.erExtraWeight[unit] * 2 : 0) +
payloadLive.empty +
SBResponse.message.pax * (PaxConfig.weights.pax[unit] + PaxConfig.weights.baggage[unit]) +
SBResponse.message.cargo
);
const _fuel = SBResponse.message.fuel;
updateView(
PaxConfig.distribute(__ZFW, targetZFWCG, payloadLive.empty, fuelLive, unit, isER, SBResponse.message.pax)
);
setSBPlan(SBResponse.message);
setZFW(__ZFW);
setFuel(_fuel);
setSBInFlight(false);
};
useEffect(
() =>
setFuel((prev) =>
prev > (isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit])
? isER
? SharedConfig.maxFuel.er[unit]
: SharedConfig.maxFuel.norm[unit]
: prev
),
[isER]
);
return (
<>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>Planned ZFW ({unit})</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={SBPlan?.plannedZFW ?? 0}
disabled
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-700 p-2 px-4">
<label>Planned ZFW ({unit})</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={SBPlan?.plannedGW ?? 0}
disabled
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-600 p-2 px-4">
<label>
Target ZFWCG ({SharedConfig.CGLimits.min} - {SharedConfig.CGLimits.max})
</label>
<CGSelect
value={targetZFWCG}
disabled={loadingState !== 'preview'}
increase={() =>
setTargetZFWCG((prev) => {
const _new = prev + 0.1;
updateView(PaxConfig.distribute(ZFW, _new, payloadLive.empty, fuelLive, unit, isER));
return _new;
})
}
decrease={() =>
setTargetZFWCG((prev) => {
const _new = prev - 0.1;
updateView(PaxConfig.distribute(ZFW, _new, payloadLive.empty, fuelLive, unit, isER));
return _new;
})
}
/>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-md bg-zinc-600 p-2 px-4">
<label>Fuel ({unit})</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right`}
value={fuel}
onChange={(e) =>
handleInput(
e.target.value,
isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit],
setFuel
)
}
disabled
/>
<button
className="middle none center rounded-lg bg-green-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-green-500/20 transition-all hover:shadow-lg hover:shadow-green-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={() => {
SimVar.SetSimVarValue('L:MD11_EFB_PAYLOAD_FUEL', 'lbs', unit === 'kg' ? fuel * 2.20462262185 : fuel);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
}}
disabled={loadingState !== 'preview' || SBInFlight}
>
Load Fuel
</button>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>
{loadingState !== 'loaded' ? 'Expected' : 'Actual'} ZFW ({unit})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${ZFWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={_ZFW()}
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>
{loadingState !== 'loaded' ? 'Expected' : 'Actual'} GW ({unit})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${GWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={GW()}
/>
</div>
</div>
<ActionBar
loadingState={loadingState}
acceptDisabled={!GWValid() || SBInFlight}
//TODO: Make GSX optional (accepted state for NON GSX)
accept={() => setLoadingState('loaded')}
reject={() => setLoadingState('preview')}
importSB={handleSB}
load={() => {
setLoadingState('loaded');
loadAircraft();
}}
unload={() => {
setLoadingState('preview');
PaxConfig.unload(unit, isER);
}}
/>
</>
);
};
export default SBEntryPax;
@@ -0,0 +1,73 @@
import { FC } from 'react';
interface ActionBarProps {
loadingState: 'preview' | 'accepted' | 'loaded';
acceptDisabled: boolean;
accept: () => void;
reject: () => void;
importSB?: () => void;
load: () => void;
unload: () => void;
}
const ActionBar: FC<ActionBarProps> = ({ loadingState, acceptDisabled, accept, reject, importSB, load, unload }) => {
return (
<div className="relative flex w-full items-center justify-start gap-x-6">
{loadingState === 'preview' && (
<button
className="middle none center rounded-lg bg-green-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-green-500/20 transition-all hover:shadow-lg hover:shadow-green-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={accept}
disabled={acceptDisabled}
>
Accept
</button>
)}
{/*TODO: Make GSX optional (accepted state for NON GSX) */}
{loadingState === 'loaded' && (
<button
className="middle none center rounded-lg bg-red-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-red-500/20 transition-all hover:shadow-lg hover:shadow-red-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={reject}
>
Reject
</button>
)}
<div className="grow" />
{!!importSB && loadingState === 'preview' && (
<button
className="middle none center rounded-lg bg-green-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-green-500/20 transition-all hover:shadow-lg hover:shadow-green-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={importSB}
>
Import from SimBrief
</button>
)}
{/*TODO: Make GSX optional */}
{/*
{loadingState === 'accepted' && (
<button
className="middle none center rounded-lg bg-green-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-green-500/20 transition-all hover:shadow-lg hover:shadow-green-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={load}
>
Load
</button>
)}
{loadingState === 'loaded' && (
<button
className="middle none center rounded-lg bg-red-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-red-500/20 transition-all hover:shadow-lg hover:shadow-red-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={unload}
>
Unload
</button>
)}
*/}
</div>
);
};
export default ActionBar;
@@ -0,0 +1,92 @@
import { FC, useState } from 'react';
import { PayloadFreight } from '../../configs/freighter';
import { initialPayload, SharedConfig } from '../../configs/shared';
import Profile from '../profile/Profile';
import Tabbar from '../tabbar/Tabbar';
interface FreightProps {
isER: boolean;
unit: 'kg' | 'lbs';
OEW: number;
CGs: [number, number];
}
const Freight: FC<FreightProps> = ({ isER, unit, OEW, CGs }) => {
const [selectedTab, setSelectedTab] = useState(0);
const [payload, setPayload] = useState<PayloadFreight>(initialPayload);
const [inPreview, setInPreview] = useState(true);
const upper1 = () => {
return Math.round(payload.upper1Left + payload.upper1Right);
};
const upper2 = () => {
return Math.round(payload.upper2Left + payload.upper2Right);
};
const upper3 = () => {
return Math.round(payload.upper3Left + payload.upper3Right);
};
const upper4 = () => {
return Math.round(payload.upper4Left + payload.upper4Right);
};
const lower1 = () => {
return Math.round(payload.lowerForward);
};
const lower2 = () => {
return Math.round(payload.lowerRear);
};
const _OEW = () => {
return Math.round(OEW + (isER ? SharedConfig.erExtraWeight[unit] * 2 : 1));
};
const crew = () => {
return Math.round(payload.pilot + payload.firstOfficer + payload.engineer);
};
const cgs = (): [string, string] => {
return [CGs[0].toFixed(1), CGs[1].toFixed(1)];
};
return (
<>
<Profile
type="F"
isER={isER}
upper1={`${upper1()}`}
upper2={`${upper2()}`}
upper3={`${upper3()}`}
upper4={`${upper4()}`}
lower1={`${lower1()}`}
lower2={`${lower2()}`}
OEW={`${_OEW()}`}
crew={`${crew()}`}
unit={unit.toUpperCase()}
inPreview={inPreview}
CGs={cgs()}
/>
<Tabbar tabs={['Simbrief', 'ZFW', 'Cargo']} selectedTab={selectedTab} setSelectedTab={setSelectedTab} />
<div className="relative flex w-full items-center justify-start gap-x-6">
<button
className="middle none center rounded-lg bg-green-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-green-500/20 transition-all hover:shadow-lg hover:shadow-green-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={() => {
console.log('TODO: SET PAYLOAD IN SIM');
setInPreview(false);
}}
>
Load
</button>
<button
className="middle none center rounded-lg bg-red-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-red-500/20 transition-all hover:shadow-lg hover:shadow-red-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={() => {
console.log('TODO: CLEAR PAYLOAD IN SIM');
setInPreview(true);
}}
>
Unload
</button>
</div>
</>
);
};
export default Freight;
@@ -0,0 +1,235 @@
import { FC, useEffect, useState } from 'react';
import { PaxConfig, PayloadPax } from '../../configs/pax';
import { Fuel, initialPayload, SharedConfig } from '../../configs/shared';
import Profile from '../profile/Profile';
import SBEntryPax from '../SBEntry/SBEntryPax';
import StationEntryPax from '../stationEntry/StationEntryPax';
import Tabbar from '../tabbar/Tabbar';
import ZFWEntryPax from '../ZFWEntry/ZFWEntryPax';
interface PaxProps {
isER: boolean;
unit: 'kg' | 'lbs';
CGs: [number, number];
payloadLive: PayloadPax;
fuelLive: Fuel;
username?: string;
GSXPaxNum: number;
GSXCargoPercent: number;
GSXState: 'boarding' | 'deboarding' | 'idle';
}
const Pax: FC<PaxProps> = ({
isER,
unit,
CGs,
fuelLive,
payloadLive,
username,
GSXPaxNum,
GSXCargoPercent,
GSXState,
}) => {
const [selectedTab, setSelectedTab] = useState(0);
const [payload, setPayload] = useState<PayloadPax>(initialPayload);
const [loadingState, setLoadingState] = useState<'preview' | 'accepted' | 'loaded'>('preview');
const upper1 = (overrideState: 'preview' | 'accepted' | 'loaded' = loadingState) => {
if (overrideState !== 'loaded')
return PaxConfig.weightToPax(payload.business1Left + payload.business1Center + payload.business1Right, unit);
return PaxConfig.weightToPax(
payloadLive.business1Left + payloadLive.business1Center + payloadLive.business1Right,
unit
);
};
const upper2 = (overrideState: 'preview' | 'accepted' | 'loaded' = loadingState) => {
if (overrideState !== 'loaded')
return PaxConfig.weightToPax(payload.business2Left + payload.business2Center + payload.business2Right, unit);
return PaxConfig.weightToPax(
payloadLive.business2Left + payloadLive.business2Center + payloadLive.business2Right,
unit
);
};
const upper3 = (overrideState: 'preview' | 'accepted' | 'loaded' = loadingState) => {
if (overrideState !== 'loaded')
return PaxConfig.weightToPax(payload.economy1Left + payload.economy1Center + payload.economy1Right, unit);
return PaxConfig.weightToPax(
payloadLive.economy1Left + payloadLive.economy1Center + payloadLive.economy1Right,
unit
);
};
const upper4 = (overrideState: 'preview' | 'accepted' | 'loaded' = loadingState) => {
if (overrideState !== 'loaded')
return PaxConfig.weightToPax(payload.economy2Left + payload.economy2Center + payload.economy2Right, unit);
return PaxConfig.weightToPax(
payloadLive.economy2Left + payloadLive.economy2Center + payloadLive.economy2Right,
unit
);
};
const lower1 = () => {
if (loadingState !== 'loaded') return Math.round(payload.forwardCargo);
return Math.round(payloadLive.forwardCargo);
};
const lower2 = () => {
if (loadingState !== 'loaded') return Math.round(payload.rearCargo);
return Math.round(payloadLive.rearCargo);
};
const _OEW = () => {
if (loadingState !== 'loaded')
return Math.round(payloadLive.empty + (isER ? SharedConfig.erExtraWeight[unit] * 2 : 1));
return Math.round(payloadLive.empty + payloadLive.leftAuxPax + payloadLive.rightAuxPax);
};
const crew = () => {
if (loadingState !== 'loaded') return PaxConfig.weights.base[unit].total;
return Math.round(
payloadLive.cabinCrewFront +
payloadLive.cabinCrewRear +
payloadLive.pilot +
payloadLive.firstOfficer +
payloadLive.engineer
);
};
const _CGs = (): [string, boolean, string, boolean] => {
if (loadingState !== 'loaded') {
const __CGs = PaxConfig.calculateCGs(
{
...payload,
empty: payloadLive.empty,
cabinCrewFront: PaxConfig.weights.base[unit].cabinCrewFront,
cabinCrewRear: PaxConfig.weights.base[unit].cabinCrewRear,
pilot: PaxConfig.weights.base[unit].pilot,
firstOfficer: PaxConfig.weights.base[unit].firstOfficer,
engineer: PaxConfig.weights.base[unit].engineer,
leftAuxPax: isER ? SharedConfig.erExtraWeight[unit] : 0,
rightAuxPax: isER ? SharedConfig.erExtraWeight[unit] : 0,
},
fuelLive
);
return [
__CGs[0].toFixed(1),
__CGs[0] < SharedConfig.CGLimits.min || __CGs[0] > SharedConfig.CGLimits.max,
__CGs[1].toFixed(1),
__CGs[1] < SharedConfig.CGLimits.min || __CGs[1] > SharedConfig.CGLimits.max,
];
}
return [
CGs[0].toFixed(1),
CGs[0] < SharedConfig.CGLimits.min || CGs[0] > SharedConfig.CGLimits.max,
CGs[1].toFixed(1),
CGs[1] < SharedConfig.CGLimits.min || CGs[1] > SharedConfig.CGLimits.max,
];
};
//TODO: Make GSX optional
useEffect(() => {
if (GSXState === 'idle') return;
PaxConfig.setWeightsProgressive(
payload,
GSXState === 'boarding' ? GSXPaxNum : payload.paxCount.total - GSXPaxNum,
GSXCargoPercent,
unit
);
}, [GSXPaxNum, GSXCargoPercent, GSXState]);
return (
<>
<Profile
type="PAX"
isER={isER}
upper1={`${upper1()}`}
upper1max={loadingState === 'loaded' ? `${upper1('preview')}` : `${PaxConfig.stationMax.business1}`}
upper2={`${upper2()}`}
upper2max={loadingState === 'loaded' ? `${upper2('preview')}` : `${PaxConfig.stationMax.business2}`}
upper3={`${upper3()}`}
upper3max={loadingState === 'loaded' ? `${upper3('preview')}` : `${PaxConfig.stationMax.economy1}`}
upper4={`${upper4()}`}
upper4max={loadingState === 'loaded' ? `${upper4('preview')}` : `${PaxConfig.stationMax.economy2}`}
lower1={`${lower1()}`}
lower2={`${lower2()}`}
OEW={`${_OEW()}`}
crew={`${crew()}`}
unit={unit.toUpperCase()}
inPreview={loadingState !== 'loaded'}
CGs={_CGs()}
/>
<Tabbar
tabs={
username ? ['Simbrief', 'ZFW', 'Passengers & Cargo', 'Options'] : ['ZFW', 'Passengers & Cargo', 'Options']
}
selectedTab={selectedTab}
setSelectedTab={setSelectedTab}
/>
{username && selectedTab === 0 && (
<SBEntryPax
unit={unit}
isER={isER}
initialPayload={payload}
fuelLive={fuelLive}
payloadLive={payloadLive}
loadingState={loadingState}
username={username}
setLoadingState={setLoadingState}
updateView={(_payload) => {
setPayload(_payload);
}}
loadAircraft={() => {
PaxConfig.setBaseWeight(unit, isER);
PaxConfig.setWeights(payload, unit);
}}
/>
)}
{((username && selectedTab === 1) || (!username && selectedTab === 0)) && (
<ZFWEntryPax
unit={unit}
isER={isER}
initialPayload={payload}
fuelLive={fuelLive}
payloadLive={payloadLive}
loadingState={loadingState}
setLoadingState={setLoadingState}
updateView={(_payload) => {
setPayload(_payload);
}}
loadAircraft={() => {
PaxConfig.setBaseWeight(unit, isER);
PaxConfig.setWeights(payload, unit);
}}
/>
)}
{((username && selectedTab === 2) || (!username && selectedTab === 1)) && (
<StationEntryPax
unit={unit}
isER={isER}
initialPayload={payload}
fuelLive={fuelLive}
payloadLive={payloadLive}
loadingState={loadingState}
setLoadingState={setLoadingState}
updateView={(_payload) => {
setPayload(_payload);
}}
loadAircraft={() => {
PaxConfig.setBaseWeight(unit, isER);
PaxConfig.setWeights(payload, unit);
}}
/>
)}
</>
);
};
export default Pax;
@@ -0,0 +1,14 @@
.stroke-zinc-600 {
--tw-bg-opacity: 1;
stroke: rgba(82, 82, 91, var(--tw-bg-opacity));
}
.fill-neutral-500 {
--tw-text-opacity: 1;
fill: rgba(115, 115, 115, var(--tw-text-opacity));
}
.fill-red-500 {
--tw-bg-opacity: 1;
fill: rgba(239, 68, 68, var(--tw-bg-opacity));
}
@@ -0,0 +1,143 @@
import { FC } from 'react';
import styles from './Profile.module.scss';
interface ProfileProps {
type: 'F' | 'PAX';
isER: boolean;
upper1: string;
upper1max?: string;
upper2: string;
upper2max?: string;
upper3: string;
upper3max?: string;
upper4: string;
upper4max?: string;
lower1: string;
lower2: string;
OEW: string;
crew: string;
CGs: [string, boolean, string, boolean];
unit: string;
inPreview: boolean;
}
const Profile: FC<ProfileProps> = ({
type,
isER,
upper1,
upper1max,
upper2,
upper2max,
upper3,
upper3max,
upper4,
upper4max,
lower1,
lower2,
OEW,
crew,
CGs,
unit,
inPreview,
}) => {
const previewClass = inPreview ? styles['fill-neutral-500'] : undefined;
const ZFWCGClass = CGs[1] ? styles['fill-red-500'] : previewClass;
const TOCGClass = CGs[3] ? styles['fill-red-500'] : previewClass;
return (
<svg viewBox="0 0 4002 780" version="1.1" xmlns="http://www.w3.org/2000/svg" className="mb-4">
<path
style={{
fill: 'none',
strokeWidth: 4,
strokeLinecap: 'butt',
strokeLinejoin: 'miter',
}}
className={styles['stroke-zinc-600']}
d="m 1748.2487,624.45529 v 83.5816 z m 653.1368,98.40409 v 43.08593 z"
transform="matrix(1.0068517,0,0,1.0069072,-24.265193,-12.003831)"
/>
<path
style={{
fill: 'none',
stroke: 'white',
strokeWidth: 4,
strokeLinecap: 'butt',
strokeLinejoin: 'round',
}}
d="m 119.50997,573.71703 h 128.0729 v 29.07865 h 119.1481 M 1922.9882,624.6584 V 378.08519 Z M 1143.9838,378.54476 v 246.57126 z m 1556.0004,1.0854 3e-4,245.70686 0.01,-245.70686 z M 119.47872,507.3493 V 686.42638 Z M 681.8277,784.08016 C 550.68986,778.56008 459.37243,767.83734 325.24777,742.2102 247.12441,727.28325 163.85922,704.53832 115.68874,684.96689 65.272866,664.48337 30.640554,635.98924 26.748642,611.79065 c -3.768329,-23.42962 7.455828,-38.32595 50.49813,-67.01953 6.041298,-4.02691 12.708263,-9.27073 14.816577,-11.65295 2.108023,-2.3822 4.524361,-4.48001 4.524361,-4.48001 0,0 45.05507,-44.22564 69.74451,-58.59581 72.48275,-42.18783 238.75927,-76.44031 425.63149,-87.67895 14.00372,-0.84197 33.99829,-2.07064 44.43261,-2.72959 26.21726,-1.65646 1973.24248,-2.45294 2062.36198,-0.84445 156.7316,2.82998 269.5005,7.74134 530.5511,23.10724 47.1495,2.7752 85.8534,4.90642 86.012,4.73564 0.6463,-0.69433 3.18,-46.35302 2.6116,-47.00235 -0.3392,-0.38606 -8.4798,-1.63074 -18.0917,-2.7653 -35.3422,-4.17401 -67.399,-11.82474 -77.0779,-18.39697 -3.9158,-2.65808 -3.9645,-3.95085 -1.9416,-51.11949 2.108,-49.16104 3.1187,-81.48815 3.1285,-100.00521 0,-21.77363 -0.1657,-21.37816 11.1206,-24.94772 37.4058,-11.83133 117.8811,-11.06942 171.1122,1.62031 15.227,3.62986 19.589,3.51496 35.5372,-0.93615 21.7001,-6.05753 21.0601,-5.51568 96.6373,-82.052719 33.1386,-33.558758 68.757,-66.037569 68.757,-66.037569 0,0 83.3796,-1.079949 169.2388,-1.079949 136.9782,0 171.6165,0.253341 171.2179,1.25017 -4.4107,11.004128 -56.5812,125.499597 -58.6917,128.808777 -4.6101,7.22481 -3.2139,7.88313 16.7587,7.90201 9.0622,0.008 22.5343,1.10293 37.2289,3.0234 4.1187,0.5386 15.3536,1.65343 24.9606,2.47749 9.6122,0.82422 19.8325,1.75756 22.7175,2.07418 5.0681,0.55713 9.0514,198.40717 3.9934,198.38178 -3.6049,-0.0227 -31.7008,3.23584 -34.6991,4.01865 -1.9198,0.50162 -6.9752,1.19264 -11.2312,1.53478 -7.7386,0.62215 -7.7386,0.62215 -7.7386,8.4184 0,8.93284 0.045,8.97847 9.985,9.03089 7.595,0.034 9.8124,1.63453 3.4137,2.45321 -2.7018,0.34575 -6.0356,0.83238 -7.4075,1.08266 -1.3721,0.25018 -13.4624,0.89408 -26.8658,1.43238 -13.4009,0.53851 -28.9024,1.4418 -34.4462,2.00812 -15.6072,1.59497 -41.7208,3.01042 -55.6346,3.01554 -20.6343,0.008 -19.0304,-1.48851 -18.6619,17.4164 0.3714,19.01843 2.3134,23.86211 11.5516,28.7958 5.5848,2.98376 0.3035,2.54795 88.3399,7.28999 84.7141,4.56302 75.158,-2.81091 75.158,57.97778 0,54.09028 0.038,53.96894 -18.7912,59.84504 -20.4081,6.36957 -118.4497,34.30825 -182.9044,52.12181 -74.4105,20.56545 -152.6321,42.51798 -160.2541,44.9738 -10.5871,3.41069 -57.1162,16.38817 -105.4333,29.40502 -184.6228,49.73829 -402.0471,89.78228 -516.1218,95.05681 -19.1654,0.88577 -2300.07154,1.25614 -2320.9699,0.37695 z m 1714.3911,-15.93638 c 14.3101,-1.72862 35.0046,-5.59211 45.932,-8.57466 47.3734,-12.93067 34.3571,-20.57346 -70.3941,-41.33854 -9.3343,-1.85054 -21.9156,-4.53045 -27.9568,-5.955 -119.2244,-28.11802 -135.9358,-31.16723 -227.3866,-41.48667 -32.4779,-3.66487 -84.0049,-11.3955 -126.5748,-18.9901 -77.6003,-13.84405 -129.5471,-21.9553 -156.762,-24.47858 -178.2016,-16.52165 -260.2497,38.68714 -109.1224,73.42801 7.2586,1.66798 18.9422,5.29576 25.9629,8.06195 39.3007,15.48166 111.0211,26.35086 233.4296,35.37682 97.5092,7.1897 247.1876,10.13705 247.9345,4.88196 0.6641,-4.66361 1.511,-4.79998 36.7154,-5.88569 22.9808,-0.70847 26.3783,-0.21481 26.3783,3.83836 0,2.5877 2.9594,14.02473 3.8762,14.97665 0.4007,0.41452 15.6685,2.27843 33.9288,4.14116 18.264,1.86268 33.4272,3.60903 33.698,3.88056 0.7657,0.76635 16.5438,-0.20912 30.341,-1.87623 z m -2029.36746,-359.3222 -0.0248,215.97654 1275.64716,-4.1e-4 h 1275.1949 l 132.9522,-2.46652 132.952,-2.4665 147.9241,-11.4452 147.9243,-11.4452 1.0236,-90.88061 1.0235,-90.88061 -83.3015,-4.35422 -83.3014,-4.35424 m -2614.52231,217.81122 -5e-5,159.11801 z M 1748.1434,708.5565 v 75.45385 z m 1663.0399,-106.30033 -1e-4,113.31756 z m -1009.9445,22.54108 v 99.39317 z m -4e-4,143.38207 v 15.78331 z"
transform="matrix(1.0068517,0,0,1.0069072,-24.265193,-12.003831)"
/>
<text style={{ fill: 'white', fontSize: '160px' }} x="725.61609" y="591.34473" textAnchor="middle">
<tspan className={previewClass}>{upper1}</tspan>
{upper1max && <tspan>/{upper1max}</tspan>}
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="1509.024" y="591.34473" textAnchor="middle">
<tspan className={previewClass}>{upper2}</tspan>
{upper2max && <tspan>/{upper2max}</tspan>}
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="2292.3511" y="591.34473" textAnchor="middle">
<tspan className={previewClass}>{upper3}</tspan>
{upper3max && <tspan>/{upper3max}</tspan>}
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="3075.8955" y="591.34473" textAnchor="middle">
<tspan className={previewClass}>{upper4}</tspan>
{upper4max && <tspan>/{upper4max}</tspan>}
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="1197.6641" y="754.35504" textAnchor="middle">
<tspan className={previewClass}>{lower1}</tspan>
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="2891.1475" y="754.35504" textAnchor="middle">
<tspan className={previewClass}>{lower2}</tspan>
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="0" y="280.10175">
{type === 'F' ? 'Pilots:' : 'Pilots & FAs:'}
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="1476.2501" y="280.10175" textAnchor="end">
<tspan className={previewClass}>{crew}</tspan>
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="0" y="130.625">
OEW:
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="1476.2501" y="130.625" textAnchor="end">
{OEW}
</text>
<text style={{ fill: 'white', fontSize: '100px' }} x="4002" y="778.24402" textAnchor="end">
<tspan style={{ fontSize: '70px' }}>all in </tspan>
<tspan>{unit}</tspan>
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="1730.4194" y="142.15625">
ZFWCG:
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="2540.771" y="142.15625">
<tspan className={ZFWCGClass}>{CGs[0]}</tspan>
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="1730.4194" y="288.875">
TOCG:
</text>
<text style={{ fill: 'white', fontSize: '160px' }} x="2540.771" y="288.875">
<tspan className={TOCGClass}>{CGs[2]}</tspan>
</text>
<path
style={{
fill: 'none',
stroke: 'white',
strokeWidth: 8,
strokeLinecap: 'butt',
strokeLinejoin: 'round',
opacity: isER ? 1 : 0,
}}
d="m 3616.1269,203.67131 -115.4682,116.32951 h 76.6132 l 13.6484,-13.75016 h -61.1499 l 42.8836,-43.20365 h 61.1499 l 13.6485,-13.75016 h -61.1499 l 31.6393,-31.87538 h 61.1499 l 13.6484,-13.75016 z m 101.1357,0 -115.4682,116.32951 h 15.4631 l 45.9082,-46.25055 h 21.7891 l -7.0158,46.25055 h 20.0709 l 6.7491,-50.85999 c 10.2365,-2.8646 20.2552,-7.10948 30.0559,-12.73452 9.8522,-5.67714 18.9402,-12.70852 27.2636,-21.09401 6.1521,-6.19797 10.0211,-11.30222 11.6068,-15.31268 1.6896,-4.06255 1.7441,-7.42197 0.1635,-10.07824 -1.4223,-2.34378 -4.0367,-3.95839 -7.8438,-4.84382 -3.7552,-0.93749 -9.1472,-1.40626 -16.176,-1.40625 z m 2.2801,13.2814 h 18.1967 c 4.165,0 7.4735,0.31252 9.9248,0.93751 2.5032,0.57292 4.1244,1.61462 4.8641,3.12503 0.9495,1.77087 0.7045,3.9584 -0.735,6.56258 -1.3356,2.55212 -3.6579,5.49487 -6.9666,8.82824 -4.3426,4.37505 -8.5084,7.9949 -12.4972,10.8595 -3.8849,2.81253 -8.0847,5.31258 -12.5989,7.50009 -4.8776,2.34378 -9.5781,3.95838 -14.1015,4.84381 -4.4716,0.83333 -9.1546,1.25002 -14.0487,1.25002 h -15.6194 z"
/>
</svg>
);
};
export default Profile;
@@ -0,0 +1,309 @@
import { FC, useEffect, useState } from 'react';
import { PaxConfig, PayloadPax } from '../../configs/pax';
import { Fuel, SharedConfig } from '../../configs/shared';
import ActionBar from '../actionbar/ActionBar';
interface StationEntryProps {
unit: 'kg' | 'lbs';
isER: boolean;
initialPayload: PayloadPax;
fuelLive: Fuel;
payloadLive: PayloadPax;
loadingState: 'preview' | 'accepted' | 'loaded';
setLoadingState: (newState: StationEntryProps['loadingState']) => void;
updateView: (payload: PayloadPax) => void;
loadAircraft: () => void;
}
const StationEntryPax: FC<StationEntryProps> = ({
unit,
isER,
initialPayload,
fuelLive,
payloadLive,
loadingState,
setLoadingState,
updateView,
loadAircraft,
}) => {
const [business1, setBusiness1] = useState(
PaxConfig.weightToPax(
initialPayload.business1Left + initialPayload.business1Center + initialPayload.business1Right,
unit
)
);
const [business2, setBusiness2] = useState(
PaxConfig.weightToPax(
initialPayload.business2Left + initialPayload.business2Center + initialPayload.business2Right,
unit
)
);
const [economy1, setEconomy1] = useState(
PaxConfig.weightToPax(
initialPayload.economy1Left + initialPayload.economy1Center + initialPayload.economy1Right,
unit
)
);
const [economy2, setEconomy2] = useState(
PaxConfig.weightToPax(
initialPayload.economy2Left + initialPayload.economy2Center + initialPayload.economy2Right,
unit
)
);
const [forwardCargo, setForwardCargo] = useState(initialPayload.forwardCargo);
const [rearCargo, setRearCargo] = useState(initialPayload.rearCargo);
const [fuel, setFuel] = useState(
Math.round(
fuelLive.main1 +
fuelLive.main1Tip +
fuelLive.main2 +
fuelLive.main3 +
fuelLive.main3Tip +
fuelLive.upperAux +
fuelLive.lowerAux +
fuelLive.tail +
fuelLive.forwardAux1 +
fuelLive.forwardAux2
)
);
const ZFW = () => {
if (loadingState !== 'loaded')
return Math.round(
(business1 + business2 + economy1 + economy2) * PaxConfig.weights.pax[unit] +
forwardCargo +
rearCargo +
PaxConfig.weights.base[unit].total +
(isER ? SharedConfig.erExtraWeight[unit] * 2 : 0) +
payloadLive.empty
);
return Math.round(
payloadLive.empty +
payloadLive.pilot +
payloadLive.firstOfficer +
payloadLive.engineer +
payloadLive.cabinCrewFront +
payloadLive.business1Left +
payloadLive.business1Center +
payloadLive.business1Right +
payloadLive.business2Left +
payloadLive.business2Center +
payloadLive.business2Right +
payloadLive.economy1Left +
payloadLive.economy1Center +
payloadLive.economy1Right +
payloadLive.economy2Left +
payloadLive.economy2Center +
payloadLive.economy2Right +
payloadLive.cabinCrewRear +
payloadLive.forwardCargo +
payloadLive.rearCargo +
payloadLive.leftAuxPax +
payloadLive.rightAuxPax
);
};
const ZFWValid = () => {
return ZFW() <= PaxConfig.maxZWF[unit];
};
const GW = () => {
return fuel + ZFW();
};
const GWValid = () => {
return GW() <= (isER ? SharedConfig.maxTOW.er[unit] : SharedConfig.maxTOW.norm[unit]);
};
const handleInput = (input: string, maxValue: number, setter: (value: number) => void) => {
if (!input) {
setter(0);
return;
}
const converted = parseInt(input);
if (converted) {
if (converted < 0) setter(0);
else if (converted > maxValue) setter(maxValue);
else setter(converted);
}
};
useEffect(() => _updateView(), [business1, business2, economy1, economy2, forwardCargo, rearCargo]);
useEffect(
() =>
setFuel((prev) =>
prev > (isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit])
? isER
? SharedConfig.maxFuel.er[unit]
: SharedConfig.maxFuel.norm[unit]
: prev
),
[isER]
);
const _updateView = () => {
const payload = PaxConfig.generateDistribution(
payloadLive.empty,
business1,
business2,
economy1,
economy2,
forwardCargo,
rearCargo,
unit,
isER
);
updateView(payload);
};
return (
<>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>Business</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={business1}
onChange={(e) => handleInput(e.target.value, PaxConfig.stationMax.business1, setBusiness1)}
disabled={loadingState !== 'preview'}
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-700 p-2 px-4">
<label>Premium Economy</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={business2}
onChange={(e) => handleInput(e.target.value, PaxConfig.stationMax.business2, setBusiness2)}
disabled={loadingState !== 'preview'}
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-600 p-2 px-4">
<label>Forward Economy</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={economy1}
onChange={(e) => handleInput(e.target.value, PaxConfig.stationMax.economy1, setEconomy1)}
disabled={loadingState !== 'preview'}
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-700 p-2 px-4">
<label>Aft Economy</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={economy2}
onChange={(e) => handleInput(e.target.value, PaxConfig.stationMax.economy2, setEconomy2)}
disabled={loadingState !== 'preview'}
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-600 p-2 px-4">
<label>Forward Cargo ({unit})</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={forwardCargo}
onChange={(e) => handleInput(e.target.value, SharedConfig.stationMax.forward[unit], setForwardCargo)}
disabled={loadingState !== 'preview'}
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>Aft Cargo ({unit})</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={rearCargo}
onChange={(e) => handleInput(e.target.value, SharedConfig.stationMax.rear[unit], setRearCargo)}
disabled={loadingState !== 'preview'}
/>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-md bg-zinc-600 p-2 px-4">
<label>Fuel ({unit})</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right`}
value={fuel}
onChange={(e) =>
handleInput(
e.target.value,
isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit],
setFuel
)
}
disabled={loadingState !== 'preview'}
/>
<button
className="middle none center rounded-lg bg-green-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-green-500/20 transition-all hover:shadow-lg hover:shadow-green-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={() => {
SimVar.SetSimVarValue('L:MD11_EFB_PAYLOAD_FUEL', 'lbs', unit === 'kg' ? fuel * 2.20462262185 : fuel);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
}}
disabled={loadingState !== 'preview'}
>
Load Fuel
</button>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>
{loadingState !== 'loaded' ? 'Expected' : 'Actual'} ZFW ({unit})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${ZFWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={ZFW()}
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>
{loadingState !== 'loaded' ? 'Expected' : 'Actual'} GW ({unit})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${GWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={GW()}
/>
</div>
</div>
<ActionBar
loadingState={loadingState}
acceptDisabled={!ZFWValid() || !GWValid()}
accept={() => setLoadingState('accepted')}
reject={() => setLoadingState('preview')}
load={() => {
setLoadingState('loaded');
loadAircraft();
}}
unload={() => {
setLoadingState('preview');
PaxConfig.unload(unit, isER);
}}
/>
</>
);
};
export default StationEntryPax;
@@ -0,0 +1,27 @@
import { FC } from 'react';
interface TabbarProps {
tabs: string[];
selectedTab: number;
setSelectedTab: (tab: number) => void;
}
const Tabbar: FC<TabbarProps> = ({ tabs, selectedTab, setSelectedTab }) => {
return (
<ul className="mb-4 flex list-none flex-row flex-wrap border-b-0 pl-0">
{tabs.map((tab, i) => (
<li key={`${tab}-${i}`}>
<button
key={i}
className={`${selectedTab === i ? 'text-white underline' : 'text-neutral-500'} my-2 block border-x-0 border-b-2 border-t-0 border-slate-100 bg-zinc-900 px-7 text-sm font-medium uppercase`}
onClick={() => setSelectedTab(i)}
>
{tab}
</button>
</li>
))}
</ul>
);
};
export default Tabbar;
@@ -0,0 +1,284 @@
import { FC, useEffect, useState } from 'react';
import { PaxConfig, PayloadPax } from '../../configs/pax';
import { Fuel, SharedConfig } from '../../configs/shared';
import CGSelect from '../CGSelect/CGSelect';
import ActionBar from '../actionbar/ActionBar';
interface StationEntryProps {
unit: 'kg' | 'lbs';
isER: boolean;
initialPayload: PayloadPax;
fuelLive: Fuel;
payloadLive: PayloadPax;
loadingState: 'preview' | 'accepted' | 'loaded';
setLoadingState: (newState: StationEntryProps['loadingState']) => void;
updateView: (payload: PayloadPax) => void;
loadAircraft: () => void;
}
const ZFWEntryPax: FC<StationEntryProps> = ({
unit,
isER,
initialPayload,
fuelLive,
payloadLive,
loadingState,
setLoadingState,
updateView,
loadAircraft,
}) => {
const [targetZFWCG, setTargetZFWCG] = useState(SharedConfig.CGLimits.default);
const [fuel, setFuel] = useState(
Math.round(
fuelLive.main1 +
fuelLive.main1Tip +
fuelLive.main2 +
fuelLive.main3 +
fuelLive.main3Tip +
fuelLive.upperAux +
fuelLive.lowerAux +
fuelLive.tail +
fuelLive.forwardAux1 +
fuelLive.forwardAux2
)
);
const [ZFW, setZFW] = useState(
Math.round(
PaxConfig.weights.base[unit].total +
(isER ? SharedConfig.erExtraWeight[unit] * 2 : 0) +
payloadLive.empty +
initialPayload.business1Left +
initialPayload.business1Center +
initialPayload.business1Right +
initialPayload.business2Left +
initialPayload.business2Center +
initialPayload.business2Right +
initialPayload.economy1Left +
initialPayload.economy1Center +
initialPayload.economy1Right +
initialPayload.economy2Left +
initialPayload.economy2Center +
initialPayload.economy2Right +
initialPayload.forwardCargo +
initialPayload.rearCargo
)
);
const _ZFW = () => {
if (loadingState !== 'loaded') return ZFW;
return Math.round(
payloadLive.empty +
payloadLive.pilot +
payloadLive.firstOfficer +
payloadLive.engineer +
payloadLive.cabinCrewFront +
payloadLive.business1Left +
payloadLive.business1Center +
payloadLive.business1Right +
payloadLive.business2Left +
payloadLive.business2Center +
payloadLive.business2Right +
payloadLive.economy1Left +
payloadLive.economy1Center +
payloadLive.economy1Right +
payloadLive.economy2Left +
payloadLive.economy2Center +
payloadLive.economy2Right +
payloadLive.cabinCrewRear +
payloadLive.forwardCargo +
payloadLive.rearCargo +
payloadLive.leftAuxPax +
payloadLive.rightAuxPax
);
};
const ZFWValid = () => {
return _ZFW() <= PaxConfig.maxZWF[unit];
};
const GW = () => {
return fuel + _ZFW();
};
const GWValid = () => {
return GW() <= (isER ? SharedConfig.maxTOW.er[unit] : SharedConfig.maxTOW.norm[unit]);
};
const handleInput = (input: string, maxValue: number, setter: (value: number) => void) => {
if (!input) {
setter(0);
return;
}
const converted = parseInt(input);
if (converted) {
if (converted < 0) setter(0);
else if (converted > maxValue) setter(maxValue);
else setter(converted);
}
};
const handleInputZFW = (input: string) => {
if (!input) return;
const converted = parseInt(input);
if (converted) {
if (converted < 0)
setZFW(
Math.round(
PaxConfig.weights.base[unit].total + (isER ? SharedConfig.erExtraWeight[unit] * 2 : 0) + payloadLive.empty
)
);
else if (converted > PaxConfig.maxZWF[unit]) setZFW(PaxConfig.maxZWF[unit]);
else setZFW(converted);
}
};
const handleBlur = (input: string) => {
const minZFW = Math.round(
PaxConfig.weights.base[unit].total + (isER ? SharedConfig.erExtraWeight[unit] * 2 : 0) + payloadLive.empty
);
if (!input) {
setZFW(minZFW);
return;
}
const converted = parseInt(input);
if (converted) {
if (converted < minZFW) setZFW(minZFW);
else if (converted > PaxConfig.maxZWF[unit]) setZFW(PaxConfig.maxZWF[unit]);
else setZFW(converted);
}
updateView(PaxConfig.distribute(converted, targetZFWCG, payloadLive.empty, fuelLive, unit, isER));
};
useEffect(
() =>
setFuel((prev) =>
prev > (isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit])
? isER
? SharedConfig.maxFuel.er[unit]
: SharedConfig.maxFuel.norm[unit]
: prev
),
[isER]
);
return (
<>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>Target ZFW ({unit})</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={ZFW}
onChange={(e) => handleInputZFW(e.target.value)}
onBlur={(e) => handleBlur(e.target.value)}
disabled={loadingState !== 'preview'}
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>
Target ZFWCG ({SharedConfig.CGLimits.min} - {SharedConfig.CGLimits.max})
</label>
<CGSelect
value={targetZFWCG}
disabled={loadingState !== 'preview'}
increase={() =>
setTargetZFWCG((prev) => {
const _new = prev + 0.1;
updateView(PaxConfig.distribute(ZFW, _new, payloadLive.empty, fuelLive, unit, isER));
return _new;
})
}
decrease={() =>
setTargetZFWCG((prev) => {
const _new = prev - 0.1;
updateView(PaxConfig.distribute(ZFW, _new, payloadLive.empty, fuelLive, unit, isER));
return _new;
})
}
/>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-md bg-zinc-600 p-2 px-4">
<label>Fuel ({unit})</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right`}
value={fuel}
onChange={(e) =>
handleInput(
e.target.value,
isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit],
setFuel
)
}
disabled={loadingState !== 'preview'}
/>
<button
className="middle none center rounded-lg bg-green-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-green-500/20 transition-all hover:shadow-lg hover:shadow-green-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={() => {
SimVar.SetSimVarValue('L:MD11_EFB_PAYLOAD_FUEL', 'lbs', unit === 'kg' ? fuel * 2.20462262185 : fuel);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
}}
disabled={loadingState !== 'preview'}
>
Load Fuel
</button>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>
{loadingState !== 'loaded' ? 'Expected' : 'Actual'} ZFW ({unit})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${ZFWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={_ZFW()}
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>
{loadingState !== 'loaded' ? 'Expected' : 'Actual'} GW ({unit})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${GWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={GW()}
/>
</div>
</div>
<ActionBar
loadingState={loadingState}
acceptDisabled={!GWValid()}
accept={() => setLoadingState('accepted')}
reject={() => setLoadingState('preview')}
load={() => {
setLoadingState('loaded');
loadAircraft();
}}
unload={() => {
setLoadingState('preview');
PaxConfig.unload(unit, isER);
}}
/>
</>
);
};
export default ZFWEntryPax;