From 67d27ffe91c600e84774aee12256f381a33a1219 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Tue, 2 Sep 2025 20:57:20 -0500 Subject: [PATCH] Accounting Glowup: Advances, Pay raises and cuts, and more (#92419) ## About The Pull Request Fixes #92188 1. The Accounting console now has a new UI image 2. The Accounting console can now dish out advances on paychecks, giving you for full paycheck early. You can give up to 3 advances. This of course means your next paycheck is not paid out when relevant. 3. The Accounting console can now change paycheck sizes of crewmembers - up to 1.5x and down to 0.5x. ## Why It's Good For The Game - Gives the HoP some more duties, now being able to dish out money on request, reward good behavior, or punish bad behavior. ## Changelog :cl: Melbert add: Accounting Console: New UI! add: Accounting Console: Now can give advances to crewmembers add: Accounting Console: Can now give pay raises or pay cuts add: Accounting Console: Now only printable in the security lathe add: Accounting Console: A spare board is now now found in secure tech storage. fix: Fix vending machines adding payments to audit log twice. fix: Non-crewmembers are no longer shown in the accounting console /:cl: --------- Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com> --- code/controllers/subsystem/economy.dm | 7 +- code/datums/components/payment.dm | 2 +- code/game/machinery/computer/accounting.dm | 63 ++++++- .../effects/spawners/random/techstorage.dm | 5 +- code/modules/economy/account.dm | 37 ++-- code/modules/jobs/job_types/_job.dm | 2 +- .../job_types/station_trait/cargo_gorilla.dm | 2 +- .../research/designs/comp_board_designs.dm | 4 +- .../shuttle/mobile_port/variants/supply.dm | 2 +- code/modules/vending/vendor/inventory.dm | 1 - .../tgui/interfaces/AccountingConsole.tsx | 132 -------------- .../AccountingConsole/AuditScreen.tsx | 43 +++++ .../AccountingConsole/FakeDesktopButton.tsx | 35 ++++ .../AccountingConsole/FakeToolbarButton.tsx | 34 ++++ .../AccountingConsole/FakeWindow.tsx | 64 +++++++ .../interfaces/AccountingConsole/Sort.tsx | 39 +++++ .../AccountingConsole/UsersScreen.tsx | 151 ++++++++++++++++ .../interfaces/AccountingConsole/helpers.ts | 15 ++ .../interfaces/AccountingConsole/index.tsx | 162 ++++++++++++++++++ .../interfaces/AccountingConsole/types.ts | 42 +++++ .../tgui/styles/interfaces/Accounting.scss | 42 +++++ tgui/packages/tgui/styles/main.scss | 1 + 22 files changed, 720 insertions(+), 165 deletions(-) delete mode 100644 tgui/packages/tgui/interfaces/AccountingConsole.tsx create mode 100644 tgui/packages/tgui/interfaces/AccountingConsole/AuditScreen.tsx create mode 100644 tgui/packages/tgui/interfaces/AccountingConsole/FakeDesktopButton.tsx create mode 100644 tgui/packages/tgui/interfaces/AccountingConsole/FakeToolbarButton.tsx create mode 100644 tgui/packages/tgui/interfaces/AccountingConsole/FakeWindow.tsx create mode 100644 tgui/packages/tgui/interfaces/AccountingConsole/Sort.tsx create mode 100644 tgui/packages/tgui/interfaces/AccountingConsole/UsersScreen.tsx create mode 100644 tgui/packages/tgui/interfaces/AccountingConsole/helpers.ts create mode 100644 tgui/packages/tgui/interfaces/AccountingConsole/index.tsx create mode 100644 tgui/packages/tgui/interfaces/AccountingConsole/types.ts create mode 100644 tgui/packages/tgui/styles/interfaces/Accounting.scss diff --git a/code/controllers/subsystem/economy.dm b/code/controllers/subsystem/economy.dm index bbc72ee4470..9125936600f 100644 --- a/code/controllers/subsystem/economy.dm +++ b/code/controllers/subsystem/economy.dm @@ -157,7 +157,7 @@ SUBSYSTEM_DEF(economy) var/datum/bank_account/bank_account = cached_processing[cached_processing[i]] if(bank_account?.account_job && !ispath(bank_account.account_job)) temporary_total += (bank_account.account_job.paycheck * STARTING_PAYCHECKS) - bank_account.payday(1) + bank_account.payday(1, skippable = TRUE) station_total += bank_account.account_balance if(MC_TICK_CHECK) cached_processing.Cut(1, i + 1) @@ -214,14 +214,15 @@ SUBSYSTEM_DEF(economy) * * price_to_use: The cost of the purchase made for this transaction. * * vendor: The object or structure medium that is charging the user. For Vending machines that's the machine, for payment component that's the parent, cargo that's the crate, etc. */ -/datum/controller/subsystem/economy/proc/track_purchase(datum/bank_account/account, price_to_use, vendor) - if(!account || isnull(price_to_use) || !vendor) +/datum/controller/subsystem/economy/proc/add_audit_entry(datum/bank_account/account, price_to_use, vendor) + if(isnull(account) || isnull(price_to_use) || !vendor) CRASH("Track purchases was missing an argument! (Account, Price, or Vendor.)") audit_log += list(list( "account" = "[account.account_holder]", "cost" = price_to_use, "vendor" = "[vendor]", + "stationtime" = station_time_timestamp("hh:mm"), )) /** diff --git a/code/datums/components/payment.dm b/code/datums/components/payment.dm index 37839f1599c..edb9762ba7e 100644 --- a/code/datums/components/payment.dm +++ b/code/datums/components/payment.dm @@ -168,7 +168,7 @@ log_econ("[total_cost] credits were spent on [parent] by [user] via [idcard.registered_account.account_holder]'s card.") idcard.registered_account.bank_card_talk("[total_cost] credits deducted from your account.") playsound(src, 'sound/effects/cashregister.ogg', 20, TRUE) - SSeconomy.track_purchase(idcard.registered_account, total_cost, parent) + SSeconomy.add_audit_entry(idcard.registered_account, total_cost, parent) return TRUE /** diff --git a/code/game/machinery/computer/accounting.dm b/code/game/machinery/computer/accounting.dm index d804b8efe5d..75a890d382a 100644 --- a/code/game/machinery/computer/accounting.dm +++ b/code/game/machinery/computer/accounting.dm @@ -1,3 +1,7 @@ +#define MAX_ADVANCES 3 +#define MIN_PAY_MOD 0.5 +#define MAX_PAY_MOD 1.5 + /obj/machinery/computer/accounting name = "account lookup console" desc = "Used to view crew member accounts and purchases." @@ -17,17 +21,60 @@ . = ..() var/list/data = list() var/list/player_accounts = list() - var/list/audit_list = SSeconomy.audit_log - for(var/current_account as anything in SSeconomy.bank_accounts_by_id) - var/datum/bank_account/current_bank_account = SSeconomy.bank_accounts_by_id[current_account] + for(var/id in SSeconomy.bank_accounts_by_id) + var/datum/bank_account/current_bank_account = SSeconomy.bank_accounts_by_id[id] + if(!(current_bank_account.account_job?.job_flags & JOB_CREW_MANIFEST)) + continue player_accounts += list(list( "name" = current_bank_account.account_holder, - "job" = current_bank_account.account_job?.title || "No job", // because this can be null + "job" = current_bank_account.account_job.title, "balance" = round(current_bank_account.account_balance), - "modifier" = round((current_bank_account.payday_modifier * 0.9), 0.1), + "modifier" = current_bank_account.payday_modifier, + "num_advances" = current_bank_account.paydays_to_skip, + "id" = id, )) - data["PlayerAccounts"] = player_accounts - data["AuditLog"] = audit_list - data["Crashing"] = HAS_TRAIT(SSeconomy, TRAIT_MARKET_CRASHING) + data["accounts"] = player_accounts + data["audit_log"] = SSeconomy.audit_log + data["crashing"] = HAS_TRAIT(SSeconomy, TRAIT_MARKET_CRASHING) + data["station_time"] = station_time_timestamp("hh:mm") return data + +/obj/machinery/computer/accounting/ui_static_data(mob/user) + var/list/data = list() + var/static/ian_format = pick("png", "jpg", "jpeg", "webp", "bmp") + data["pic_file_format"] = ian_format + data["young_ian"] = check_holidays(IAN_HOLIDAY) + data["max_advances"] = MAX_ADVANCES + data["max_pay_mod"] = MAX_PAY_MOD + data["min_pay_mod"] = MIN_PAY_MOD + return data + +/obj/machinery/computer/accounting/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + playsound(src, SFX_TERMINAL_TYPE, 50, FALSE) + var/datum/bank_account/bank_account = SSeconomy.bank_accounts_by_id[params["account_id"]] + if(isnull(bank_account) || !(bank_account.account_job?.job_flags & JOB_CREW_MANIFEST)) + return + + switch(action) + if("paycheck_advance") + if(bank_account.paydays_to_skip < MAX_ADVANCES) + bank_account.payday(1, event = "Paycheck advance") + bank_account.paydays_to_skip += 1 + return TRUE + if("change_pay_mod") + var/old_modifier = bank_account.payday_modifier + bank_account.payday_modifier = clamp(round(text2num(params["pay_mod"]), 0.05), MIN_PAY_MOD, MAX_PAY_MOD) + var/new_check_total = bank_account.payday_modifier * bank_account.account_job.paycheck + var/raise_or_cut = new_check_total > old_modifier * bank_account.account_job.paycheck ? "raised" : "cut" + bank_account.bank_card_talk("Paycheck [raise_or_cut] to [new_check_total]cr.") + SSeconomy.add_audit_entry(bank_account, new_check_total, "Paycheck [raise_or_cut]") + return TRUE + +#undef MAX_ADVANCES +#undef MIN_PAY_MOD +#undef MAX_PAY_MOD diff --git a/code/game/objects/effects/spawners/random/techstorage.dm b/code/game/objects/effects/spawners/random/techstorage.dm index 2fd7808847c..d114e071901 100644 --- a/code/game/objects/effects/spawners/random/techstorage.dm +++ b/code/game/objects/effects/spawners/random/techstorage.dm @@ -126,9 +126,10 @@ /obj/effect/spawner/random/techstorage/command_all name = "secure command circuit board spawner" loot = list( - /obj/item/circuitboard/computer/crew, - /obj/item/circuitboard/computer/communications, + /obj/item/circuitboard/computer/accounting, /obj/item/circuitboard/computer/bankmachine, + /obj/item/circuitboard/computer/communications, + /obj/item/circuitboard/computer/crew, ) /obj/effect/spawner/random/techstorage/rnd_secure_all diff --git a/code/modules/economy/account.dm b/code/modules/economy/account.dm index 8ec8e5b2d8a..986def70031 100644 --- a/code/modules/economy/account.dm +++ b/code/modules/economy/account.dm @@ -37,6 +37,8 @@ var/list/transaction_history = list() ///A lazylist of coupons redeemed with the Coupon Master pda app associated with this account. var/list/redeemed_coupons + /// How many paychecks to skip when payday is called. + var/paydays_to_skip = 0 /datum/bank_account/New(newname, job, modifier = 1, player_account = TRUE) account_holder = newname @@ -188,10 +190,21 @@ * Arguments: * * amount_of_paychecks - literally the number of salaries, 1 for issuing one salary, 5 for issuing five salaries. * * free - issuance of free funds, if TRUE then takes funds from the void, if FALSE (default) tries to send from the department's account. + * * skippable - if TRUE, this proc may pay out nothing if the account has paydays_to_skip + * * event - the name of the event that is being processed, used for bank card messages. */ -/datum/bank_account/proc/payday(amount_of_paychecks, free = FALSE) +/datum/bank_account/proc/payday(amount_of_paychecks, free = FALSE, skippable = FALSE, event = "Payday") if(!account_job) - return + return FALSE + + if(skippable && !free) + while(paydays_to_skip > 0 && amount_of_paychecks > 0) + amount_of_paychecks -= 1 + paydays_to_skip -= 1 + + if(amount_of_paychecks <= 0) + return FALSE + var/money_to_transfer = round(account_job.paycheck * payday_modifier * amount_of_paychecks) if(amount_of_paychecks == 1) money_to_transfer = clamp(money_to_transfer, 0, PAYCHECK_CREW) //We want to limit single, passive paychecks to regular crew income. @@ -201,17 +214,15 @@ SSeconomy.station_target += money_to_transfer log_econ("[money_to_transfer] credits were given to [src.account_holder]'s account from income.") return TRUE - else - var/datum/bank_account/department_account = SSeconomy.get_dep_account(account_job.paycheck_department) - if(department_account) - if(!transfer_money(department_account, money_to_transfer)) - bank_card_talk("ERROR: Payday aborted, departmental funds insufficient.") - return FALSE - else - bank_card_talk("Payday processed, account now holds [account_balance] cr.") - return TRUE - bank_card_talk("ERROR: Payday aborted, unable to contact departmental account.") - return FALSE + var/datum/bank_account/department_account = SSeconomy.get_dep_account(account_job.paycheck_department) + if(isnull(department_account)) + bank_card_talk("ERROR: [event] aborted, unable to contact departmental account.") + return FALSE + if(!transfer_money(department_account, money_to_transfer)) + bank_card_talk("ERROR: [event] aborted, departmental funds insufficient.") + return FALSE + bank_card_talk("[event] processed, account now holds [account_balance] cr.") + return TRUE /** * This sends a local chat message to the owner of a bank account, on all ID cards registered to the bank_account. diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 869c59c7044..449392abe26 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -203,7 +203,7 @@ /mob/living/carbon/human/on_job_equipping(datum/job/equipping, client/player_client) if(equipping.paycheck_department) var/datum/bank_account/bank_account = new(real_name, equipping, dna.species.payday_modifier) - bank_account.payday(STARTING_PAYCHECKS, TRUE) + bank_account.payday(STARTING_PAYCHECKS, free = TRUE) account_id = bank_account.account_id bank_account.replaceable = FALSE add_mob_memory(/datum/memory/key/account, remembered_id = account_id) diff --git a/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm b/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm index 4f0af0d2d06..c11c931a3ef 100644 --- a/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm +++ b/code/modules/jobs/job_types/station_trait/cargo_gorilla.dm @@ -34,7 +34,7 @@ . = ..() // Gorilla with a wage, what's he buyin? var/datum/bank_account/bank_account = new(spawned.real_name, src) - bank_account.payday(STARTING_PAYCHECKS, TRUE) + bank_account.payday(STARTING_PAYCHECKS, free = TRUE) bank_account.replaceable = FALSE spawned.add_mob_memory(/datum/memory/key/account, remembered_id = bank_account.account_id) diff --git a/code/modules/research/designs/comp_board_designs.dm b/code/modules/research/designs/comp_board_designs.dm index 078e3aabcc0..2daa2404c08 100644 --- a/code/modules/research/designs/comp_board_designs.dm +++ b/code/modules/research/designs/comp_board_designs.dm @@ -392,9 +392,9 @@ build_type = IMPRINTER build_path = /obj/item/circuitboard/computer/accounting category = list( - RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_RECORDS + RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_COMMAND ) - departmental_flags = DEPARTMENT_BITFLAG_SERVICE + departmental_flags = DEPARTMENT_BITFLAG_SECURITY //Honestly should have a bridge techfab for this sometime. /datum/design/board/shuttle category = list("Computer Boards", "Shuttle Machinery") diff --git a/code/modules/shuttle/mobile_port/variants/supply.dm b/code/modules/shuttle/mobile_port/variants/supply.dm index c48ab01178e..a6c5030605a 100644 --- a/code/modules/shuttle/mobile_port/variants/supply.dm +++ b/code/modules/shuttle/mobile_port/variants/supply.dm @@ -218,7 +218,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( if(spawning_order.charge_on_purchase) receiver_message += " [price] credits have been charged to your bank account" paying_for_this.bank_card_talk(receiver_message) - SSeconomy.track_purchase(paying_for_this, price, spawning_order.pack.name) + SSeconomy.add_audit_entry(paying_for_this, price, spawning_order.pack.name) var/datum/bank_account/department/cargo = SSeconomy.get_dep_account(ACCOUNT_CAR) cargo.adjust_money(price - pack_cost) //Cargo gets the handling fee value += pack_cost diff --git a/code/modules/vending/vendor/inventory.dm b/code/modules/vending/vendor/inventory.dm index 0777ea2b160..f180058f8a6 100644 --- a/code/modules/vending/vendor/inventory.dm +++ b/code/modules/vending/vendor/inventory.dm @@ -278,7 +278,6 @@ var/datum/bank_account/paying_id_account = SSeconomy.get_dep_account(payment_department) if(paying_id_account) SSblackbox.record_feedback("amount", "vending_spent", price_to_use) - SSeconomy.track_purchase(account, price_to_use, name) log_econ("[price_to_use] credits were inserted into [src] by [account.account_holder] to buy [product_to_vend].") credits_contained += round(price_to_use * VENDING_CREDITS_COLLECTION_AMOUNT) return TRUE diff --git a/tgui/packages/tgui/interfaces/AccountingConsole.tsx b/tgui/packages/tgui/interfaces/AccountingConsole.tsx deleted file mode 100644 index 11d1fbdd0c1..00000000000 --- a/tgui/packages/tgui/interfaces/AccountingConsole.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { useState } from 'react'; -import { - Blink, - BlockQuote, - Collapsible, - Modal, - Section, - Stack, - Tabs, -} from 'tgui-core/components'; -import type { BooleanLike } from 'tgui-core/react'; - -import { useBackend } from '../backend'; -import { Window } from '../layouts'; - -type Data = { - PlayerAccounts: PlayerAccount[]; - AuditLog: AuditLog[]; - Crashing: BooleanLike; -}; - -type PlayerAccount = { - index: number; - name: string; - balance: number; - job: string; - modifier: number; -}; - -type AuditLog = { - index: number; - account: number; - cost: number; - vendor: string; -}; - -enum SCREENS { - users, - audit, -} - -export const AccountingConsole = (props) => { - const [screenmode, setScreenmode] = useState(SCREENS.users); - - return ( - - - - - - - setScreenmode(SCREENS.users)} - > - Users - - setScreenmode(SCREENS.audit)} - > - Audit - - - - - {screenmode === SCREENS.users && } - {screenmode === SCREENS.audit && } - - - - - ); -}; - -const UsersScreen = (props) => { - const { data } = useBackend(); - const { PlayerAccounts } = data; - - return ( -
- {PlayerAccounts.map((account) => ( - - -
- - {account.balance} credit balance - - - Employee has {account.modifier * 100}% pay modifier - -
-
-
- ))} -
- ); -}; - -const AuditScreen = (props) => { - const { data } = useBackend(); - const { AuditLog } = data; - - return ( -
- {AuditLog.map((purchase) => ( -
- {purchase.account} spent {purchase.cost} cr at{' '} - {purchase.vendor}. -
- ))} -
- ); -}; - -/** The modal menu that contains the prompts to making new channels. */ -const MarketCrashing = (props) => { - const { data } = useBackend(); - - const { Crashing } = data; - if (!Crashing) { - return null; - } - return ( - - OH GOD THE ECONOMY IS RUINED. - - ); -}; diff --git a/tgui/packages/tgui/interfaces/AccountingConsole/AuditScreen.tsx b/tgui/packages/tgui/interfaces/AccountingConsole/AuditScreen.tsx new file mode 100644 index 00000000000..d6799d9291f --- /dev/null +++ b/tgui/packages/tgui/interfaces/AccountingConsole/AuditScreen.tsx @@ -0,0 +1,43 @@ +import { Blink, Modal, Section, Table } from 'tgui-core/components'; +import { useBackend } from '../../backend'; +import { getRandomDoomMessage } from './helpers'; +import type { Data } from './types'; + +export const AuditScreen = () => { + const { data } = useBackend(); + const { crashing, audit_log } = data; + + return ( +
+ {!!crashing && ( + + + {getRandomDoomMessage()} + + + )} + + + Account + Cost + Location + Timestamp + + {audit_log.map((purchase, index) => ( + + {purchase.account} + + {purchase.cost} cr + + + {purchase.vendor} + + + {purchase.stationtime || '00:00'} ST + + + ))} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AccountingConsole/FakeDesktopButton.tsx b/tgui/packages/tgui/interfaces/AccountingConsole/FakeDesktopButton.tsx new file mode 100644 index 00000000000..b429fa33d23 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AccountingConsole/FakeDesktopButton.tsx @@ -0,0 +1,35 @@ +import { Button, Stack } from 'tgui-core/components'; + +import { useBackend } from '../../backend'; +import type { SCREENS } from './types'; + +type FakeDesktopButtonProps = { + children: React.ReactNode; + name: string; + setScreenmode: (mode: SCREENS) => void; + ownerScreenMode: SCREENS; +}; + +export const FakeDesktopButton = (props: FakeDesktopButtonProps) => { + const { act } = useBackend(); + const { children, name, setScreenmode, ownerScreenMode } = props; + + return ( + <> + + + + + {name} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/AccountingConsole/FakeToolbarButton.tsx b/tgui/packages/tgui/interfaces/AccountingConsole/FakeToolbarButton.tsx new file mode 100644 index 00000000000..edea840372e --- /dev/null +++ b/tgui/packages/tgui/interfaces/AccountingConsole/FakeToolbarButton.tsx @@ -0,0 +1,34 @@ +import { Button } from 'tgui-core/components'; +import { useBackend } from '../../backend'; +import type { SCREENS } from './types'; + +type FakeToolbarButtonProps = { + name: string; + currentScreenMode: SCREENS; + setScreenmode: (mode: SCREENS) => void; + ownerScreenMode: SCREENS; +}; + +export const FakeToolbarButton = (props: FakeToolbarButtonProps) => { + const { act } = useBackend(); + const { name, currentScreenMode, setScreenmode, ownerScreenMode } = props; + + return ( + + ); +}; diff --git a/tgui/packages/tgui/interfaces/AccountingConsole/FakeWindow.tsx b/tgui/packages/tgui/interfaces/AccountingConsole/FakeWindow.tsx new file mode 100644 index 00000000000..da9ee0865bc --- /dev/null +++ b/tgui/packages/tgui/interfaces/AccountingConsole/FakeWindow.tsx @@ -0,0 +1,64 @@ +import { Box, Button, DmIcon, Stack } from 'tgui-core/components'; +import { useBackend } from '../../backend'; +import { type Data, SCREENS } from './types'; + +type FakeWindowProps = { + name: string; + setScreenmode: (mode: SCREENS) => void; +}; +export const FakeWindowIan = (props: FakeWindowProps) => { + const { data } = useBackend(); + const { young_ian } = data; + + return ( + + + + ); +}; +export const FakeWindow = ( + props: FakeWindowProps & { + children: React.ReactNode; + }, +) => { + const { act } = useBackend(); + const { name, children, setScreenmode } = props; + + return ( + + + + + {name} + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/AccountingConsole/UsersScreen.tsx b/tgui/packages/tgui/interfaces/AccountingConsole/UsersScreen.tsx new file mode 100644 index 00000000000..271ecfb014a --- /dev/null +++ b/tgui/packages/tgui/interfaces/AccountingConsole/UsersScreen.tsx @@ -0,0 +1,151 @@ +import { + Blink, + Button, + Modal, + NumberInput, + Section, + Stack, + Table, +} from 'tgui-core/components'; +import { useBackend, useSharedState } from '../../backend'; +import { getRandomDoomMessage } from './helpers'; +import { SortButton } from './Sort'; +import { type Data, SORTING } from './types'; + +export const UsersScreen = () => { + const { act, data } = useBackend(); + const { crashing, accounts, max_pay_mod, min_pay_mod, max_advances } = data; + + const [accountNameSorting, setAccountNameSorting] = useSharedState( + 'sorting_account_name', + SORTING.ascending, + ); + const [balanceSorting, setBalanceSorting] = useSharedState( + 'sorting_balance', + SORTING.none, + ); + const [jobSorting, setJobSorting] = useSharedState( + 'sorting_job', + SORTING.none, + ); + + const accountsSorted = accounts.sort((a, b) => { + if (accountNameSorting === SORTING.ascending) { + return a.name > b.name ? 1 : -1; + } else if (accountNameSorting === SORTING.descending) { + return a.name > b.name ? -1 : 1; + } else if (balanceSorting === SORTING.ascending) { + return a.balance - b.balance; + } else if (balanceSorting === SORTING.descending) { + return b.balance - a.balance; + } else if (jobSorting === SORTING.ascending) { + return a.job > b.job ? 1 : -1; + } else if (jobSorting === SORTING.descending) { + return a.job > b.job ? -1 : 1; + } + return 0; + }); + + return ( +
+ {!!crashing && ( + + + {getRandomDoomMessage()} + + + )} + + + + + Account + + + + + + + + Balance + + + + + + + + Job + + + + + + Pay + Advances + + {accountsSorted.map((account, index) => ( + + {account.name} + + {account.balance} cr + + + {account.job} + + + + act('change_pay_mod', { + account_id: account.id, + pay_mod: value, + }) + } + /> + + + + {account.num_advances} + + + + + + + ))} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AccountingConsole/helpers.ts b/tgui/packages/tgui/interfaces/AccountingConsole/helpers.ts new file mode 100644 index 00000000000..6b75c73ffba --- /dev/null +++ b/tgui/packages/tgui/interfaces/AccountingConsole/helpers.ts @@ -0,0 +1,15 @@ +const doomMessages = [ + 'BUY GOLD!', + 'BUY LOW, SELL HIGH!', + 'INVEST IN CRYPTO!', + 'SELL EVERYTHING!', + 'THE ECONOMY IS COLLAPSING!', + 'THE ECONOMY IS RUINED!', + 'THE MARKET IS CRASHING!', + 'THE STATION IS GOING BANKRUPT!', +]; + +// Used when the economy is crashing to get a random funny message. +export function getRandomDoomMessage(): string { + return doomMessages[Math.floor(Math.random() * doomMessages.length)]; +} diff --git a/tgui/packages/tgui/interfaces/AccountingConsole/index.tsx b/tgui/packages/tgui/interfaces/AccountingConsole/index.tsx new file mode 100644 index 00000000000..064e3eff952 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AccountingConsole/index.tsx @@ -0,0 +1,162 @@ +import { Button, DmIcon, Stack } from 'tgui-core/components'; + +import { useBackend, useSharedState } from '../../backend'; +import { Window } from '../../layouts'; +import { AuditScreen } from './AuditScreen'; +import { FakeDesktopButton } from './FakeDesktopButton'; +import { FakeToolbarButton } from './FakeToolbarButton'; +import { FakeWindow, FakeWindowIan } from './FakeWindow'; +import { type Data, SCREENS } from './types'; +import { UsersScreen } from './UsersScreen'; + +export const AccountingConsole = () => { + const { data } = useBackend(); + const { + station_time = '00:00', + pic_file_format = 'png', + young_ian = false, + } = data; + const [screenmode, setScreenmode] = useSharedState('screen', SCREENS.none); + + const ianFileName = young_ian + ? `Ian's first birthday.${pic_file_format}` + : `Ian.${pic_file_format}`; + + return ( + + + + + + + + + + + + + + + + + + + + + + + {screenmode === SCREENS.users && ( + + + + + + )} + {screenmode === SCREENS.audit && ( + + + + + + )} + {screenmode === SCREENS.ian && ( + + + + )} + + + + + + + + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/AccountingConsole/types.ts b/tgui/packages/tgui/interfaces/AccountingConsole/types.ts new file mode 100644 index 00000000000..5d714b180ed --- /dev/null +++ b/tgui/packages/tgui/interfaces/AccountingConsole/types.ts @@ -0,0 +1,42 @@ +import type { BooleanLike } from 'tgui-core/react'; + +export type Data = { + accounts: PlayerAccount[]; + audit_log: AuditLog[]; + crashing: BooleanLike; + pic_file_format: string; + max_pay_mod: number; + min_pay_mod: number; + max_advances: number; + station_time: string; + young_ian: BooleanLike; +}; + +type PlayerAccount = { + id: number; + name: string; + balance: number; + job: string; + modifier: number; + num_advances: number; +}; + +type AuditLog = { + account: number; + cost: number; + vendor: string; + stationtime: string; +}; + +export enum SCREENS { + none, + users, + audit, + ian, +} + +export enum SORTING { + ascending, + descending, + none, +} diff --git a/tgui/packages/tgui/styles/interfaces/Accounting.scss b/tgui/packages/tgui/styles/interfaces/Accounting.scss new file mode 100644 index 00000000000..d48ef846d73 --- /dev/null +++ b/tgui/packages/tgui/styles/interfaces/Accounting.scss @@ -0,0 +1,42 @@ +.Accounting__Toolbar { + outline: solid 1px; + outline-style: outset; + outline-width: 2px; + outline-color: hsl(0, 0%, 85%); + background-color: rgb(195, 195, 195); + color: grey; +} + +.Accounting__Window { + flex-direction: column; + outline: solid 1px black; + outline-style: outset; + outline-width: 2px; + outline-color: hsl(0, 0%, 85%); + background-color: rgb(195, 195, 195); +} + +.Accounting__WindowContent { + background-color: white; + outline: solid; + outline-style: outset; + outline-width: 2px; + outline-color: hsl(0, 0%, 85%); + background-color: rgb(195, 195, 195); +} + +.Accounting__TableHeader { + border-style: solid; + border-width: 2px; + border-left: 0px; + border-right: 0px; + border-bottom: 0px; +} + +.Accounting__TableCellSides { + border-style: solid; + border-width: 1px; + border-top: 0px; + border-right: 0px; + border-bottom: 0px; +} diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index ad5d9498735..67289ecb747 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -16,6 +16,7 @@ @include meta.load-css('~tgui-core/styles/all-themes.scss'); // Interfaces +@include meta.load-css('./interfaces/Accounting.scss'); @include meta.load-css('./interfaces/AlertModal.scss'); @include meta.load-css('./interfaces/Changelog.scss'); @include meta.load-css('./interfaces/CrewManifest.scss');