diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm index 4070301bfbe..86a4d4f3a4f 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -92,26 +92,17 @@ /obj/machinery/computer/cargo/express/proc/packin_up(forced = FALSE) // oh shit, I'm sorry meme_pack_data = list() // sorry for what? - if (!forced && !SSshuttle.initialized) // Subsystem is still sleeping, add ourselves to its buffer and abort - SSshuttle.express_consoles += src - return - for(var/pack in SSshuttle.supply_packs) // our quartermaster taught us not to be ashamed of our supply packs - var/datum/supply_pack/P = SSshuttle.supply_packs[pack] // specially since they're such a good price and all - if(!meme_pack_data[P.group]) // yeah, I see that, your quartermaster gave you good advice - meme_pack_data[P.group] = list( // it gets cheaper when I return it - "name" = P.group, // mmhm - "packs" = list() // sometimes, I return it so much, I rip the manifest - ) // see, my quartermaster taught me a few things too - if((P.hidden) || (P.special)) // like, how not to rip the manifest - continue// by using someone else's crate - if(P.contraband && !contraband) // will you show me? - continue // i'd be right happy to - meme_pack_data[P.group]["packs"] += list(list( - "name" = P.name, - "cost" = P.get_cost() * get_discount(), - "id" = pack, - "desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name. - )) + if(!forced && !SSshuttle.initialized) // our quartermaster taught us not to be ashamed of our supply packs + SSshuttle.express_consoles += src // specially since they're such a good price and all + return // yeah, I see that, your quartermaster gave you good advice + // it gets cheaper when I return it + for(var/pack_id in SSshuttle.supply_packs) // mmhm + var/datum/supply_pack/pack = SSshuttle.supply_packs[pack_id] // sometimes, I return it so much, I rip the manifest + if(!meme_pack_data[pack.group]) // see, my quartermaster taught me a few things too + meme_pack_data[pack.group] = list( // like, how not to rip the manifest + "name" = pack.group, // by using someone else's crate + "packs" = get_packs_data(pack.group, express = TRUE), // will you show me? + ) // i'd be right happy to /obj/machinery/computer/cargo/express/ui_data(mob/user) var/canBeacon = beacon && (isturf(beacon.loc) || ismob(beacon.loc))//is the beacon in a valid location? @@ -146,7 +137,7 @@ data["supplies"] = meme_pack_data return data -/obj/machinery/computer/cargo/express/proc/get_discount() +/obj/machinery/computer/cargo/express/get_discount() return (obj_flags & EMAGGED) ? EXPRESS_EMAG_DISCOUNT : 1 /obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui) diff --git a/code/modules/cargo/orderconsole.dm b/code/modules/cargo/orderconsole.dm index 87a085707c0..409dc197e43 100644 --- a/code/modules/cargo/orderconsole.dm +++ b/code/modules/cargo/orderconsole.dm @@ -95,9 +95,9 @@ var/list/data = list() data["department"] = "Cargo" // Hardcoded here, for customization in budgetordering.dm AKA NT IRN data["location"] = SSshuttle.supply.getStatusText() - var/datum/bank_account/D = SSeconomy.get_dep_account(cargo_account) - if(D) - data["points"] = D.account_balance + var/datum/bank_account/bank = SSeconomy.get_dep_account(cargo_account) + if(bank) + data["points"] = bank.account_balance data["grocery"] = SSshuttle.chef_groceries.len data["away"] = SSshuttle.supply.getDockedId() == docking_away data["self_paid"] = self_paid @@ -162,26 +162,78 @@ var/list/data = list() data["max_order"] = CARGO_MAX_ORDER data["supplies"] = list() - for(var/pack in SSshuttle.supply_packs) - var/datum/supply_pack/P = SSshuttle.supply_packs[pack] - if(!data["supplies"][P.group]) - data["supplies"][P.group] = list( - "name" = P.group, - "packs" = list() + + for(var/pack_id in SSshuttle.supply_packs) + var/datum/supply_pack/pack = SSshuttle.supply_packs[pack_id] + if(!data["supplies"][pack.group]) + data["supplies"][pack.group] = list( + "name" = pack.group, + "packs" = get_packs_data(pack.group), ) - if((P.hidden && !(obj_flags & EMAGGED)) || (P.contraband && !contraband) || (P.special && !P.special_enabled) || P.drop_pod_only) - continue - data["supplies"][P.group]["packs"] += list(list( - "name" = P.name, - "cost" = P.get_cost(), - "id" = pack, - "desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name. - "goody" = P.goody, - "access" = P.access, - "contraband" = P.contraband, - )) + return data +/** + * returns a list of supply packs for a certain group + * * group - the group of packs to return + * * express - if this is an express console + */ +/obj/machinery/computer/cargo/proc/get_packs_data(group, express = FALSE) + var/list/packs = list() + for(var/pack_id in SSshuttle.supply_packs) + var/datum/supply_pack/pack = SSshuttle.supply_packs[pack_id] + if(pack.group != group) + continue + + // Express console packs check + if(express && (pack.hidden || pack.special)) + continue + + if(!express && ((pack.hidden && !(obj_flags & EMAGGED)) || (pack.special && !pack.special_enabled) || pack.drop_pod_only)) + continue + + if(pack.contraband && !contraband) + continue + + var/obj/item/first_item = length(pack.contains) > 0 ? pack.contains[1] : null + packs += list(list( + "name" = pack.name, + "cost" = pack.get_cost() * get_discount(), + "id" = pack_id, + "desc" = pack.desc || pack.name, // If there is a description, use it. Otherwise use the pack's name. + "first_item_icon" = first_item?.icon, + "first_item_icon_state" = first_item?.icon_state, + "goody" = pack.goody, + "access" = pack.access, + "contraband" = pack.contraband, + "contains" = get_pack_contains(pack), + )) + + return packs + +/** + * returns a list of the contents of a supply pack + * * pack - the pack to get the contents of + */ +/obj/machinery/computer/cargo/proc/get_pack_contains(datum/supply_pack/pack) + var/list/contains = list() + for(var/obj/item/item as anything in pack.contains) + contains += list(list( + "name" = item.name, + "icon" = item.greyscale_config ? null : item.icon, + "icon_state" = item.greyscale_config ? null : item.icon_state, + "amount" = pack.contains[item] + )) + + return contains + +/** + * returns the discount multiplier applied to all supply packs, + * the discount is calculated as follows: pack_cost * get_discount() + */ +/obj/machinery/computer/cargo/proc/get_discount() + return 1 + /** * adds an supply pack to the checkout cart * * user - the mobe doing this order diff --git a/tgui/packages/tgui/interfaces/Cargo/CargoCart.tsx b/tgui/packages/tgui/interfaces/Cargo/CargoCart.tsx index 41c05acdb05..af3fc11981e 100644 --- a/tgui/packages/tgui/interfaces/Cargo/CargoCart.tsx +++ b/tgui/packages/tgui/interfaces/Cargo/CargoCart.tsx @@ -11,7 +11,6 @@ import { import { formatMoney } from 'tgui-core/format'; import { useBackend } from '../../backend'; -import { CargoCartButtons } from './CargoButtons'; import { CargoData } from './types'; export function CargoCart(props) { @@ -23,16 +22,18 @@ export function CargoCart(props) { return ( - }> + {cart.length > 0 && !!can_send && ( - - + + - {!sendable && } + {!sendable && ( + + )} {entry.object} - {can_send && entry.can_be_cancelled ? ( - - act('modify', { - order_name: entry.object, - amount: value, - }) - } - /> - ) : ( - - )} - - {!!can_send && !!entry.can_be_cancelled && ( + {!!can_send && !!entry.can_be_cancelled ? ( <> + act('remove', { order_name: entry.object })} + /> + + act('modify', { + order_name: entry.object, + amount: value, + }) + } + /> = max_order} @@ -108,11 +108,9 @@ function CheckoutItems(props) { act('add_by_name', { order_name: entry.object }) } /> - act('remove', { order_name: entry.object })} - /> > + ) : ( + )} diff --git a/tgui/packages/tgui/interfaces/Cargo/CargoCatalog.tsx b/tgui/packages/tgui/interfaces/Cargo/CargoCatalog.tsx index 3806320f8a1..f498b1c21f4 100644 --- a/tgui/packages/tgui/interfaces/Cargo/CargoCatalog.tsx +++ b/tgui/packages/tgui/interfaces/Cargo/CargoCatalog.tsx @@ -1,19 +1,20 @@ import { sortBy } from 'common/collections'; -import { useMemo } from 'react'; +import { Dispatch, SetStateAction, useMemo, useState } from 'react'; import { + BlockQuote, Button, Icon, - Input, + ImageButton, + Modal, Section, Stack, - Table, Tabs, Tooltip, } from 'tgui-core/components'; import { formatMoney } from 'tgui-core/format'; import { useBackend, useSharedState } from '../../backend'; -import { CargoCartButtons } from './CargoButtons'; +import { SearchBar } from '../common/SearchBar'; import { searchForSupplies } from './helpers'; import { CargoData, Supply, SupplyCategory } from './types'; @@ -22,19 +23,17 @@ type Props = { }; export function CargoCatalog(props: Props) { + const { data } = useBackend(); const { express } = props; - const { act, data } = useBackend(); - const { self_paid } = data; const supplies = Object.values(data.supplies); - + const [showContents, setShowContents] = useState(''); + const [searchText, setSearchText] = useSharedState('search_text', ''); const [activeSupplyName, setActiveSupplyName] = useSharedState( 'supply', supplies[0]?.name, ); - const [searchText, setSearchText] = useSharedState('search_text', ''); - const packs = useMemo(() => { let fetched: Supply[] | undefined; @@ -54,42 +53,35 @@ export function CargoCatalog(props: Props) { }, [activeSupplyName, supplies, searchText]); return ( - - - act('toggleprivate')} - tooltip="Use your own funds to purchase items." - > - Buy Privately - - > - ) - } - > + <> + {showContents && ( + + )} - - + + + + - - + + + + - + > ); } @@ -101,143 +93,245 @@ type CatalogTabsProps = { setSearchText: (text: string) => void; }; -function CatalogTabs(props: CatalogTabsProps) { +function CatalogTabs(props: CatalogTabsProps & Props) { + const { act, data } = useBackend(); const { activeSupplyName, categories, searchText, setActiveSupplyName, setSearchText, + express, } = props; + const { self_paid } = data; const sorted = sortBy(categories, (supply) => supply.name); return ( - - - - - - - - { - if (value === searchText) { - return; - } + + + { + if (value === searchText) { + return; + } - if (value.length) { - // Start showing results - setActiveSupplyName('search_results'); - } else if (activeSupplyName === 'search_results') { - // return to normal category - setActiveSupplyName(sorted[0]?.name); - } - setSearchText(value); - }} - /> - - - - - {sorted.map((supply) => ( - { - setActiveSupplyName(supply.name); - setSearchText(''); + if (value.length) { + // Start showing results + setActiveSupplyName('search_results'); + } else if (activeSupplyName === 'search_results') { + // return to normal category + setActiveSupplyName(sorted[0]?.name); + } + setSearchText(value); }} - > - - {supply.name} - {supply.packs.length} - - - ))} - + /> + + + + + + {sorted.map((supply) => ( + { + setActiveSupplyName(supply.name); + setSearchText(''); + }} + > + + {supply.name} + {supply.packs.length} + + + ))} + + + + {!express && ( + act('toggleprivate')} + tooltip="Use your own funds to purchase items." + tooltipPosition="top" + > + Buy Privately + + )} + + ); } type CatalogListProps = { packs: SupplyCategory['packs']; + openContents: Dispatch>; }; function CatalogList(props: CatalogListProps) { const { act, data } = useBackend(); const { amount_by_name = {}, max_order, self_paid, app_cost } = data; - const { packs = [] } = props; + const { packs = [], openContents } = props; return ( - - - {packs.map((pack) => { - let color = ''; - const digits = Math.floor(Math.log10(pack.cost) + 1); - if (self_paid) { - color = 'caution'; - } else if (digits >= 5 && digits <= 6) { - color = 'yellow'; - } else if (digits > 6) { - color = 'bad'; - } + <> + {packs.map((pack) => { + let color = ''; + const digits = Math.floor(Math.log10(pack.cost) + 1); + if (self_paid) { + color = 'yellow'; + } else if (digits >= 5 && digits <= 6) { + color = 'orange'; + } else if (digits > 6) { + color = 'bad'; + } - return ( - - {pack.name} - - {!!pack.small_item && ( - - - - )} - - - {!!pack.access && ( - - - - )} - {!!pack.contraband && ( - - - - )} - - - = max_order} - onClick={() => - act('add', { - id: pack.id, - }) - } - minWidth={5} - mb={0.5} - > - {formatMoney( - (self_paid && !pack.goody) || app_cost - ? Math.round(pack.cost * 1.1) - : pack.cost, - )}{' '} - cr - - - - ); - })} - - + const privateBuy = (self_paid && !pack.goody) || app_cost; + const tooltipIcon = (content: string, icon: string, color: string) => ( + + + + + + ); + + return ( + = max_order} + buttonsAlt={ + openContents(pack.name)} + /> + } + onClick={() => + act('add', { + id: pack.id, + }) + } + > + + + {pack.name} + + {(!!pack.small_item || !!pack.access || !!pack.contraband) && ( + + + {!!pack.small_item && + tooltipIcon('Small Item', 'compress-alt', 'purple')} + {!!pack.access && + tooltipIcon('Restricted', 'lock', 'average')} + {!!pack.contraband && + tooltipIcon('Contraband', 'pastafarianism', 'bad')} + + + )} + + + + {formatMoney(pack.cost)} cr + + {!!privateBuy && ( + + {formatMoney(Math.round(pack.cost * 1.1))} cr + + )} + + + + + ); + })} + > + ); +} + +type CatalogContentsProps = { + name: string; + closeContents: Dispatch>; + packs: SupplyCategory['packs']; +}; + +function CatalogPackInfo(props: CatalogContentsProps) { + const { name, packs, closeContents } = props; + const pack = packs.find((pack) => pack.name === name); + const contains = pack?.contains; + + return ( + + + + closeContents('')} + /> + } + > + {pack?.desc || 'No description available.'} + + + + + {contains && contains.length > 0 ? ( + contains.map((item) => ( + + x{item.amount} + + ) + } + imageSize={32} + > + + {item.name} + + + )) + ) : ( + + + + + + {`We can't find information about even the approximate contents + of this order.`} + + + )} + + + + ); } diff --git a/tgui/packages/tgui/interfaces/Cargo/CargoRequests.tsx b/tgui/packages/tgui/interfaces/Cargo/CargoRequests.tsx index c088a11d063..6bfe62806fc 100644 --- a/tgui/packages/tgui/interfaces/Cargo/CargoRequests.tsx +++ b/tgui/packages/tgui/interfaces/Cargo/CargoRequests.tsx @@ -10,22 +10,7 @@ export function CargoRequests(props) { const { requests = [], requestonly, can_send, can_approve_requests } = data; return ( - act('denyall')} - > - Clear - - ) - } - > + {requests.length === 0 && No Requests} {requests.length > 0 && ( diff --git a/tgui/packages/tgui/interfaces/Cargo/CargoStatus.tsx b/tgui/packages/tgui/interfaces/Cargo/CargoStatus.tsx index 649f4979af3..c1fbbb835f1 100644 --- a/tgui/packages/tgui/interfaces/Cargo/CargoStatus.tsx +++ b/tgui/packages/tgui/interfaces/Cargo/CargoStatus.tsx @@ -30,7 +30,7 @@ export function CargoStatus(props) { + formatMoney(value)} diff --git a/tgui/packages/tgui/interfaces/Cargo/index.tsx b/tgui/packages/tgui/interfaces/Cargo/index.tsx index 22fb5305b6d..94d90dd16f1 100644 --- a/tgui/packages/tgui/interfaces/Cargo/index.tsx +++ b/tgui/packages/tgui/interfaces/Cargo/index.tsx @@ -1,7 +1,9 @@ -import { Stack, Tabs } from 'tgui-core/components'; +import { Button, Section, Stack, Tabs } from 'tgui-core/components'; +import { toTitleCase } from 'tgui-core/string'; import { useBackend, useSharedState } from '../../backend'; import { Window } from '../../layouts'; +import { CargoCartButtons } from './CargoButtons'; import { CargoCart } from './CargoCart'; import { CargoCatalog } from './CargoCatalog'; import { CargoHelp } from './CargoHelp'; @@ -11,7 +13,7 @@ import { CargoData } from './types'; enum TAB { Catalog = 'catalog', - Requests = 'requests', + Requests = 'active requests', Cart = 'cart', Help = 'help', } @@ -27,10 +29,8 @@ export function Cargo(props) { } export function CargoContent(props) { - const { data } = useBackend(); - + const { act, data } = useBackend(); const { cart = [], requests = [], requestonly } = data; - const [tab, setTab] = useSharedState('cargotab', TAB.Catalog); let amount = 0; @@ -44,44 +44,66 @@ export function CargoContent(props) { - - setTab(TAB.Catalog)} - > - Catalog - - 0 && 'yellow'} - selected={tab === TAB.Requests} - onClick={() => setTab(TAB.Requests)} - > - Requests ({requests.length}) - - {!requestonly && ( + - 0 && 'yellow'} - selected={tab === TAB.Cart} - onClick={() => setTab(TAB.Cart)} - > - Checkout ({amount}) - - setTab(TAB.Help)} - > - Help - + {tab === TAB.Requests && !requestonly && ( + act('denyall')} + > + Clear + + )} + {(tab === TAB.Catalog || tab === TAB.Cart) && ( + + )} > - )} - + } + > + + setTab(TAB.Catalog)} + > + Catalog + + 0 && 'yellow' + } + selected={tab === TAB.Requests} + onClick={() => setTab(TAB.Requests)} + > + Requests ({requests.length}) + + {!requestonly && ( + <> + 0 && 'yellow'} + selected={tab === TAB.Cart} + onClick={() => setTab(TAB.Cart)} + > + Checkout ({amount}) + + setTab(TAB.Help)} + > + Help + + > + )} + + - + {tab === TAB.Catalog && } {tab === TAB.Requests && } {tab === TAB.Cart && } diff --git a/tgui/packages/tgui/interfaces/Cargo/types.ts b/tgui/packages/tgui/interfaces/Cargo/types.ts index f374117cbdf..1761fdf2a1e 100644 --- a/tgui/packages/tgui/interfaces/Cargo/types.ts +++ b/tgui/packages/tgui/interfaces/Cargo/types.ts @@ -31,11 +31,21 @@ export type Supply = { access: BooleanLike; cost: number; desc: string; + first_item_icon: string | null; + first_item_icon_state: string | null; goody: BooleanLike; id: string; name: string; small_item: BooleanLike; contraband: BooleanLike; + contains: SupplyItem[]; +}; + +type SupplyItem = { + name: string; + icon: string | null; + icon_state: string | null; + amount: number; }; type CartEntry = { diff --git a/tgui/packages/tgui/interfaces/CargoExpress.tsx b/tgui/packages/tgui/interfaces/CargoExpress.tsx index 4d911e0f25f..be2360a059f 100644 --- a/tgui/packages/tgui/interfaces/CargoExpress.tsx +++ b/tgui/packages/tgui/interfaces/CargoExpress.tsx @@ -2,15 +2,17 @@ import { AnimatedNumber, Box, Button, + Icon, LabeledList, + NoticeBox, Section, + Stack, } from 'tgui-core/components'; import { BooleanLike } from 'tgui-core/react'; import { useBackend } from '../backend'; import { Window } from '../layouts'; import { CargoCatalog } from './Cargo/CargoCatalog'; -import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; type Data = { locked: BooleanLike; @@ -18,21 +20,41 @@ type Data = { using_beacon: BooleanLike; beaconzone: string; beaconName: string; + beaconError: BooleanLike; canBuyBeacon: BooleanLike; hasBeacon: BooleanLike; + canBeacon: BooleanLike; printMsg: string; message: string; }; export function CargoExpress(props) { const { data } = useBackend(); - const { locked } = data; + const { beaconError, canBeacon, message, locked } = data; return ( - - - {!locked && } + + {locked ? ( + + + + + + {`Swipe a Cargo Technician-level ID card to unlock this interface.`} + + + + ) : ( + + + {message} + + + + + + )} ); @@ -42,7 +64,6 @@ function CargoExpressContent(props) { const { act, data } = useBackend(); const { hasBeacon, - message, points, using_beacon, beaconzone, @@ -52,36 +73,43 @@ function CargoExpressContent(props) { } = data; return ( - <> - - - {' credits'} - - } - > - - - act('LZCargo')}> - Cargo Bay - - act('LZBeacon')} - > - {beaconzone} ({beaconName}) - - act('printBeacon')}> - {printMsg} - - - {message} - - - - > + + + + + {' credits'} + + } + > + + + act('LZCargo')}> + Cargo Bay + + act('LZBeacon')} + > + {beaconName} + + act('printBeacon')} + > + {printMsg} + + + + + + + + + ); }
{pack?.desc || 'No description available.'}