diff --git a/.github/workflows/.gitkeep b/.github/workflows/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflows/ftp-deploy.yml b/.github/workflows/ftp-deploy.yml new file mode 100644 index 0000000..b61c3d5 --- /dev/null +++ b/.github/workflows/ftp-deploy.yml @@ -0,0 +1,32 @@ +on: + push: + branches: + - main + +name: 🚀 Deploy website via FTP + +jobs: + web-deploy: + name: 🎉 Deploy + runs-on: ubuntu-latest + if: ${{ !contains(github.event.head_commit.message, '#skipGA') }} + + steps: + - name: 🚚 Get latest code + uses: actions/checkout@v4 + + - name: 📂 Sync files + uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + with: + # dry-run: true + server: ftpupload.net + port: 21 + protocol: ftps + username: if0_38156084 + password: ${{ secrets.ftp_password }} + server-dir: /solowebworks.com/htdocs/tvt/ + exclude: | + **/.git* + **/.git*/** + **/data/** + README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee4548c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode/ +backup/ +debug.log +data/* +!data/.gitkeep diff --git a/README.md b/README.md index ac68559..8ec8b77 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,112 @@ -# Torn Vault Tracker +# Torn Vault Tracker icon Torn Vault Tracker -Track vault balances for shared private island vaults in [Torn City](https://torn.com/). +![PHP Version](https://img.shields.io/badge/PHP-8.3%2B-blue.svg) +![Database](https://img.shields.io/badge/Database-SQLite3-blue.svg) +[Deployed with FTP Deploy Action](https://github.com/SamKirkland/FTP-Deploy-Action) + +A web-based tracking system for monitoring shared vault transactions in the [Torn City MMORPG](https://torn.com/). ## Description -Uses the [Torn API](https://www.torn.com/api.html) to pul the most recent vault transactions for you and your spouse. Note that in order to access the "Log" endpoint, the keys used must be created with Full Access permissions. +This tool automatically tracks deposits and withdrawals from your Torn City private island vault using the official Torn API. It provides real-time balance tracking, available vault space calculations, and a transaction history display for multiple users. -## Getting Started +Key Features: -### Dependencies +- 🕒 Automatic transaction synchronization via Torn API +- 💰 Real-time balance calculations (individual & combined) +- 📊 Color-coded transaction history (green deposits/red withdrawals) +- 🛡️ Secure API key management +- 📈 Vault space monitoring with individual user shares +- 💾 SQLite database for persistent storage -* Any reasonable LAMP stack (developed on Ubuntu 20.04 with Apache and PHP 8.1.10) +## Features -### Installing +### Core Functionality -* Create folder in your server's web root, for example, 'tvt'. -* Upload/clone this repo into your folder. -* Edit `functions.php` to add API keys and Torn user names for you and your spouse. -* Edit `vault.csv` to set initial balances for each user (see notes below). -* Open the tracker in any web browser. -* Style as you see fit in `style.css` (Optional, but if you make something cool, let me know!). +- Automated API polling for vault transaction logs +- Duplicate transaction prevention +- Responsive HTML/CSS interface +- Error handling for API failures and database issues +- Automatic database schema creation and history population -### Initial vault balance(s) -Currently, there's no way to get past vault transactions via the API. If you're starting new, you can use the vault balance before your spouse adds their first deposit. For existing vaults, you'll need to figure out the balances the hard way. Once you have the balances, put them into the `vault.csv` with the proper user names. The timestamp field doesn't matter, and can be left at the current value. +### Financial Tracking -### Automating transaction updates +- Individual user balances +- Total vault balance calculation +- Remaining vault space tracking (500M per user / 1B total) +- Historical transaction records with timestamps -I recommend using an external site monitoring service such as [cronitor.io](https://cronitor.io) set to check the URL of your server. A check rate of 15 minutes should be more than enough, since you can always force a refresh by simply opening the site in your browser. +## Installation -## Authors +### Requirements -Keith Solomon / [ZarathosX75](https://www.torn.com/profiles.php?XID=2606457) on [Torn](https://torn.com/) +- Web server with PHP 8.1+ +- SQLite3 extension enabled +- Write permissions for web server user -## Version History +> [!NOTE] +> If you need hosting, I recommend [InfinityFree](https://www.infinityfree.com/). They have a robust free hosting package that has everything you need to run this project (I run my personal copy of this project on their service). -* 0.1 - * Initial Release +### Setup Steps + +1. Upload the project files to your web hosting. You can do this with an FTP client or by using `git` directly on the server (if your host supports it). + - To use `git`: Clone repository to web directory (the command as written will put all files in a `tvt` directory): + + ```bash + git clone https://github.com/yourusername/tvt.git + ``` + +2. Open `config.php` and configure the API keys and other settings. +3. Access the project in your web browser to initialize the database and start tracking transactions. The first run will create the database and import the vault transactions for both users. + +Depending on the number of transactions, this may take some time (my dev install took about 25 seconds to pull in ~2,100 transactions). The browser will refresh when the process is complete. + +### Configuration + + > [!IMPORTANT] + > API keys **must** have "Full Access" permissions to access `log` endpoints. + +Edit `config.php` to set up API access: + +- `username1` and `username2` should be changed to your usernames. + + ```php + const USER_KEYS = [ + 'username1' => 'your_api_key_here', + 'username2' => 'partner_api_key_here' + ]; + ``` + +- If you want to change the number of rows per page in the transactions table (default is 15), change the `ROWS_PER_PAGE` constant + +## Usage + +Access the tracker through your web browser after setup and configuration: + +- Transactions will automatically sync on page load going forward +- Balance calculations update in real-time +- Recent transactions display in chronological order + +### Contribution + +We welcome contributions! Please follow these guidelines: + +- Fork the repository +- Create feature branch (git checkout -b feature/improvement) +- Follow existing PHP coding standards +- Test changes thoroughly +- Submit a pull request + +### Credits + +Primary Author: [Keith Solomon](https://KeithSolomon.net) +(_[ZarathosX75](https://www.torn.com/profiles.php?XID=2606457) in Torn City_) + +### Acknowledgements + +- [Torn City API documentation](https://www.torn.com/api.html) +- [Torn City API Development Forum](https://www.torn.com/forums.php?p=forums&f=63&b=0&a=0) +- [Roboto font by Christian Robertson](https://fonts.google.com/specimen/Roboto) + +> [!IMPORTANT] +> **Disclaimer:** This project is not affiliated with or endorsed by Torn City diff --git a/config.php b/config.php new file mode 100644 index 0000000..8802734 --- /dev/null +++ b/config.php @@ -0,0 +1,16 @@ + 'y2ypLEEIOweEXC7x', + 'symos' => 'lne9TZLuqvFXtK3V' +]; + +// Number of rows per page +define('ROWS_PER_PAGE', 25); diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/favicon.png b/favicon.png new file mode 100644 index 0000000..5562a49 Binary files /dev/null and b/favicon.png differ diff --git a/functions.php b/functions.php index 3318ae7..f0b6468 100644 --- a/functions.php +++ b/functions.php @@ -1,229 +1,199 @@ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -$lines = file('vault.csv', FILE_IGNORE_NEW_LINES); - -foreach ($lines as $key => $value) { - $csv[$key] = str_getcsv($value); -} - -// Get user log entries. -$user1Txn = getLog(USER1_NAME, $csv); -$user2Txn = getLog(USER2_NAME, $csv); - -// Merge user arrays into transaction array -$txnVault = array_merge($user1Txn, $user2Txn); - -// Build array of entries not already in the CSV -foreach ($txnVault as $key => $value) { - $csvComp = searchForId($value['timestamp'], $csv); - - if (!$csvComp) { - $csvVault[] = $value; - } -} - -// Store new trasnactions in CSV -foreach ($csvVault as $fields) { - fputcsv($fp, $fields); -} - -fclose($fp); - -// searchForID(): Serach CSV array for timestamp ID -function searchForId($id, $array) { - foreach ($array as $key => $val) { - if ($val[1] == $id) { - return true; + $pdo->exec($createTableSQL); + } catch (PDOException $e) { + die("Database connection failed: " . $e->getMessage()); } } - return false; + return $pdo; } -// formatMoney(): Format raw transaction vaule to display as money -// cents: 0=never, 1=if needed, 2=always -function formatMoney($number, $cents = 1) { - if (is_numeric($number)) { - if (!$number) { - $money = ($cents == 2 ? '0.00' : '0'); - } else { - if (floor($number) == $number) { - $money = number_format($number, ($cents == 2 ? 2 : 0)); - } else { - $money = number_format(round($number, 2), ($cents == 0 ? 0 : 2)); - } +/** Pulls and stores all vault transaction logs for a user from the Torn API. + * + * This function retrieves all transaction logs related to vault deposits and + * withdrawals for a specified user. It continues fetching logs until no more + * entries are available, and stores each entry in the database. Each log entry + * is uniquely identified by its ID and includes details such as timestamp, + * description, and amount. The function requires the user's API key to + * authenticate requests to the Torn API. + * + * @param string $user The user whose transaction logs are to be retrieved and stored. + */ +function firstRun($user) { + $apiKey = USER_KEYS[$user]; + + $pdo = getDatabaseConnection(); + + $to = time(); + + do { + $url = "https://api.torn.com/v2/user?selections=log&log=5850,5851&to=$to"; + $ch = curl_init($url); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'accept: application/json', + "Authorization: ApiKey $apiKey" + ]); + + $response = curl_exec($ch); + + curl_close($ch); + + $data = json_decode($response, true); + + if (empty($data['log'])) { break; } + + foreach ($data['log'] as $key => $entry) { + $description = $entry['title']; + $timestamp = $entry['timestamp']; + $amount = isset($entry['data']['deposited']) ? $entry['data']['deposited'] : -$entry['data']['withdrawn']; + + $stmt = $pdo->prepare('INSERT INTO vault (ID, user, timestamp, description, amount) VALUES (:id, :user, :timestamp, :description, :amount)'); + + $stmt->bindValue(':id', $key); + $stmt->bindValue(':user', $user); + $stmt->bindValue(':timestamp', $timestamp); + $stmt->bindValue(':description', $description); + $stmt->bindValue(':amount', $amount); + + $stmt->execute(); } - if (substr($number,0,1) == '-') { - $money = ltrim($money, '-'); - $sign = '-$'; - } else { - $sign = '$'; - } - - return $sign.$money; - } + $to = end($data['log'])['timestamp']; + } while (true); } -// getLog(): Get log entries for user. Returns array. -function getLog($user, $csv) { - $tornURL = 'https://api.torn.com/user/?selections=log&key='; - $i = 0; +/** Retrieves the user's log entries from the Torn API + * + * @param string $user The user to retrieve the log for + * @param boolean $debug Whether to output debug information (default: false) + * + * @throws Exception If the user does not have an API key configured, + * if the API call fails, or if the log data is invalid + */ +function getLog($user, $debug = false) { + $pdo = getDatabaseConnection(); - if ($user == USER1_NAME) { - $usrKey = USER1_KEY; - $user = USER1_NAME; + ensureUserHasApiKey($user); + + $stmt = $pdo->query("SELECT MAX(timestamp) AS max_timestamp FROM vault WHERE user = '$user'"); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + $from = $row['max_timestamp']; + $to = time(); + + $apiKey = USER_KEYS[$user]; + $apiEndpoint = "https://api.torn.com/user/?selections=log&log=5850,5851&to=$to&from=$from&key=$apiKey"; + + $responseData = executeApiCall($apiEndpoint); + validateApiResponse($responseData); + + $checkStmt = prepareCheckStatement($pdo); + $insertStmt = prepareInsertStatement($pdo); + + processLogEntries($responseData['log'], $user, $checkStmt, $insertStmt, $debug); +} + +/** Generate the current balance for a user or all users + * + * @param string $name (optional) The user to get the balance for. If not provided, all users' balances will be added. + * + * @return string The balance as a formatted string + */ +function generateBalance($name=null) { + $balance = vaultLoop($name); + + return number_format($balance, 0); // Format the balance as an integer +} + +/** Calculate the available vault space for a user or all users. + * + * This function calculates the remaining space in the vault based on the + * transactions for a specific user or all users. If a user is specified, + * the available space is calculated against a limit of 500,000,000. + * If no user is specified, the available space is calculated against a + * total vault limit of 1,000,000,000. + * + * @param string|null $name (optional) The user to calculate the vault space for. + * If not provided, calculates for all users. + * + * @return string The available vault space as a formatted string. + */ + +function getSpace($name=null) { + $space = vaultLoop($name); + + if ($name === null) { + $space = 1000000000 - $space; } else { - $usrKey = USER2_KEY; - $user = USER2_NAME; + $space = 500000000 - $space; } - $usrURL = $tornURL . $usrKey; - $usrLog = json_decode(file_get_contents($usrURL)); - $usrData = $usrLog->log; - $usrVault = []; + return number_format($space, 0); // Format the balance as an integer +} - foreach ($usrData as $entry) { - if ($entry->log == 5851) { - $usrVault[$i]['user'] = $user; - $usrVault[$i]['timestamp'] = $entry->timestamp; - $usrVault[$i]['operation'] = $entry->title; - $usrVault[$i]['amount'] = '-'.$entry->data->withdrawn; - } elseif ($entry->log == 5850) { - $usrVault[$i]['user'] = $user; - $usrVault[$i]['timestamp'] = $entry->timestamp; - $usrVault[$i]['operation'] = $entry->title; - $usrVault[$i]['amount'] = $entry->data->deposited; +/** Builds an HTML table of all transactions in the vault + * + * Loops over all records in the vault and builds a table of the user, timestamp, + * description and amount of each transaction. Amounts are formatted as integers. + * + * @return void + */ +function buildTable () { + $records = fetchVaultRecords(); + + foreach ($records as $entry) { + $user = $entry['user']; + $timestamp = $entry['timestamp']; + $description = $entry['description']; + $amount = $entry['amount']; + $sign = getSign($amount); + + if ($description == 'Vault withdraw') { + $class = 'debit'; + $amount = substr($amount, 1); // Remove the negative sign } else { - continue; + $class = 'credit'; } - $i++; + $amount = number_format($amount, 0); // Format the amount as an integer + + echo ''; + echo ''.$user.''; + echo ''.date("F j, Y / H:i", $timestamp).''; + echo ''.$description.''; + echo ''.$sign.$amount.''; + echo ''; } - - return $usrVault; } - -// vaultTxns(): Show all vault transactions -function vaultTxns($csv) { - $output = ''; - - $output .= ''; - $output .= ' '; - $output .= ' '; - $output .= ' '; - $output .= ' '; - $output .= ' '; - $output .= ' '; - - $output .= ' '; - - $csv = array_reverse($csv); - - foreach ($csv as $entry) { - if ($entry[2] == 'Vault withdraw') { - $class = 'debit'; - } else { - $class = 'credit'; - } - - $output .= ''; - $output .= ''; - $output .= ''; - $output .= ''; - $output .= ''; - $output .= ''; - } - - $output .= ' '; - - $output .= '

User

Timestamp

Operation

Amount

'; - $output .= $entry[0]; // User - $output .= ''; - $output .= date("d/m/Y", $entry[1]); // Transaction Date - $output .= ''; - $output .= $entry[2]; // Transaction Type - $output .= ''; - $output .= formatMoney($entry[3]); // Transaction Amount - $output .= '
'; - - echo $output; -} - -// splitVault(): split CSV array into two separate arrays based on the user -function splitVault($csv, $field, $user): array { - $usrVault = []; - $usr2Vault = []; - - foreach ($csv as $entry) { - if (isset($entry[$field]) && $entry[$field] == $user) { - $usr1Vault[] = $entry; - } else { - continue; - } - } - - return $usr1Vault; -} - -// arrBalance(): Calculate balance of arrays -function arrBalance($array, $field) { - $total = 0; - - foreach ($array as $item) { - $total += $item[$field]; - } - - return $total; -} - -// vaultBalance(): Calculate overall vault balance -function vaultBalance($csv) { - $vaultBalance = arrBalance($csv,3); - $vaultSpace = formatMoney(1000000000-$vaultBalance); - - echo '
'; - echo '

Vault Balance: '.formatMoney($vaultBalance).'

'; - echo '

Vault space left: '.$vaultSpace.'

'; - echo '
'; -} - -// userBalances(): Calculate per-user vault balances -function userBalances($csv) { - $usr1 = 'Zarathos'; - $usr1Vault = splitVault($csv, '0', $usr1); - $usr1Balance = arrBalance($usr1Vault,3); - $usr1Share = formatMoney(500000000-$usr1Balance); - - $usr2 = 'Symos'; - $usr2Vault = splitVault($csv, '0', $usr2); - $usr2Balance = arrBalance($usr2Vault,3); - $usr2Share = formatMoney(500000000-$usr2Balance); - - echo '
'; - echo '

'.$usr1.' balance: '.formatMoney($usr1Balance).'

'; - echo '

'.$usr1.' share left: '.$usr1Share.'

'; - echo '
'; - - echo '
'; - echo '

'.$usr2.' balance: '.formatMoney($usr2Balance).'

'; - echo '

'.$usr2.' share left: '.$usr2Share.'

'; - echo '
'; -} -?> \ No newline at end of file diff --git a/includes/exceptions.php b/includes/exceptions.php new file mode 100644 index 0000000..5f26d40 --- /dev/null +++ b/includes/exceptions.php @@ -0,0 +1,24 @@ +query("SELECT COUNT(*) FROM vault"); + + return $stmt->fetchColumn() == 0; +} + +/** Ensures that a user has an API key configured. + * + * @param string $user The user to check the API key for + * + * @throws Exception If the user does not have an API key configured + */ +function ensureUserHasApiKey($user) { + if (!array_key_exists($user, USER_KEYS)) { + throw new ApiKeyMissingException("User does not have an API key configured."); + } +} + +/** Executes a GET request to the given API endpoint and returns the JSON response as an associative array. + * + * @param string $apiEndpoint The URL of the API endpoint to call + * + * @throws Exception If the API call fails or if the response is invalid JSON + * + * @return array The JSON response from the API + */ +function executeApiCall($apiEndpoint) { + $headers = ["Content-Type: application/json"]; + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $apiEndpoint); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + $response = curl_exec($ch); + + if (curl_errno($ch)) { + throw new CurlErrorException("cURL error: " . curl_error($ch)); + } + + curl_close($ch); + + $responseData = json_decode($response, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new JsonDataException("Failed to decode JSON response: " . json_last_error_msg()); + } + + return $responseData; +} + +/** Validates the API response data for the presence of a log array. + * + * @param array $responseData The API response data to validate. + * + * @throws Exception If the log data is missing or not an array. + */ +function validateApiResponse($responseData) { + if (!isset($responseData['log']) || !is_array($responseData['log'])) { + throw new ApiValidationException("Invalid log data received from the API."); + } +} + +/** Prepare a PDO statement for checking if a given vault transaction already exists in the database. + * + * @param PDO $pdo The PDO instance to prepare the statement with + * + * @return PDOStatement The prepared statement + */ +function prepareCheckStatement($pdo) { + $checkQuery = "SELECT COUNT(*) FROM vault WHERE user = :user AND timestamp = :timestamp AND amount = :amount"; + return $pdo->prepare($checkQuery); +} + +/** Prepare a PDO statement for inserting a new vault transaction into the database. + * + * @param PDO $pdo The PDO instance to prepare the statement with + * + * @return PDOStatement The prepared statement + */ +function prepareInsertStatement($pdo) { + $insertQuery = "INSERT INTO vault (ID, user, timestamp, description, amount) VALUES (:id, :user, :timestamp, :description, :amount)"; + return $pdo->prepare($insertQuery); +} + +/** Process an array of log entries retrieved from the Torn API. + * + * Goes through each log entry and checks if it's a vault deposit or withdrawal. + * If it is, it checks if the entry already exists in the database. If it + * doesn't, it inserts the entry into the database. + * + * @param array $logEntries The array of log entries to process + * @param string $user The user whose log entries are being processed + * @param PDOStatement $checkStmt A prepared statement to check if an entry + * already exists in the database + * @param PDOStatement $insertStmt A prepared statement to insert a new entry + * into the database + * @param boolean $debug Whether to output debug information (default: false) + */ +function processLogEntries($logEntries, $user, $checkStmt, $insertStmt, $debug) { + foreach ($logEntries as $key =>$entry) { + if ($debug) { + $logMessage = "Raw entry:\n" . print_r($entry, true); + file_put_contents(__DIR__ . '/debug.log', $logMessage, FILE_APPEND); + } + + $timestamp = $entry['timestamp']; + $description = $entry['title']; + $amount = $entry['log'] === 5850 ? $entry['data']['deposited'] : -$entry['data']['withdrawn']; + + if ($debug) { + $logMessage = "Vault entry:\n\tUser: $user,\n\tTimestamp: $timestamp,\n\tDescription: $description,\n\tAmount: $amount\n"; + file_put_contents(__DIR__ . '/debug.log', $logMessage, FILE_APPEND); + } + + $checkStmt->execute([ + ':user' => $user, + ':timestamp' => $timestamp, + ':amount' => $amount + ]); + + if ($checkStmt->fetchColumn() == 0) { + $insertStmt->execute([ + ':id' => $key, + ':user' => $user, + ':timestamp' => $timestamp, + ':description' => $description, + ':amount' => $amount + ]); + } + } +} + +/** Fetch vault records from the database + * If a user is provided, only that user's records + * are returned. Otherwise, all records are returned. + * + * @param string $user (optional) The user to filter by + * + * @return array An array of records, or an empty array on error + */ +function fetchVaultRecords($user = null) { + $pdo = getDatabaseConnection(); + + if ($user) { + $query = "SELECT * FROM vault WHERE user = :user ORDER BY timestamp DESC"; + $params = [':user' => $user]; + } else { + $query = "SELECT * FROM vault ORDER BY timestamp DESC"; + $params = []; + } + + try { + $stmt = $pdo->prepare($query); + $stmt->execute($params); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } catch (PDOException $e) { + echo "Error fetching records: " . $e->getMessage(); + + return []; + } +} + +/** Iterate over all vault records for the given user (or all users if no user is given), + * + * @param string|null $name (optional) The user to loop over records for. + * If not provided, all users' records will be looped over. + * + * @return int The total balance of the given user (or all users if no user is given). + */ +function vaultLoop ($name=null) { + $vault = fetchVaultRecords($name); + $balance = 0; + + foreach ($vault as $entry) { $balance += $entry['amount']; } + + return $balance; +} + +/** Returns a string indicating the sign of a number, with a dollar symbol. + * + * @param int|float $number The number to check. + * + * @return string A string containing a dollar sign and a negative sign if the + * number is negative. + */ +function getSign($number) { + if (substr($number, 0, 1) == '-') { return '-$'; } + + return '$'; +} diff --git a/index.php b/index.php index ef056de..caf265d 100644 --- a/index.php +++ b/index.php @@ -1,42 +1,92 @@ - + $value) { + firstRun($key); + } + header('Location: /'); +} else { + foreach (USER_KEYS as $key => $value) { + getLog($key); + } +} +?> - - - - Torn Vault Tracker + + + + Torn Vault Tracker + - + - - - + + + -

Torn Vault Tracker

+

Torn Vault Tracker

-
-
-

Transactions

+
+
+

Transactions

- -
+ + + + + + + + + -
-

Balances

+
+ + +

User

Date / Time (TCT)

Operation

Amount

+
+ + +
Page x of y
+ + +
+
- +
+

Balances

-
- -
-
-
+
+

Vault Balance: $

+

Space left: $

+
+ +

Shares

+
+ $value) { ?> +
+

balance: $

+

Share left: $

+
+ +
+
+ + + + - - \ No newline at end of file + diff --git a/script.js b/script.js new file mode 100644 index 0000000..82c9039 --- /dev/null +++ b/script.js @@ -0,0 +1,44 @@ +let currentPage = 1; + +function paginate() { + const table = document.getElementById('data'); + const rows = table.querySelectorAll('tbody tr'); + const totalPages = Math.ceil(rows.length / rowsPerPage); + + // Hide all rows + rows.forEach(row => row.style.display = 'none'); + + // Show only the rows for the current page + let startRow = (currentPage - 1) * rowsPerPage; + let endRow = Math.min(currentPage * rowsPerPage, rows.length); + + for (let i = startRow; i < endRow; i++) { + rows[i].style.display = ''; + } + + // Update pagination info + document.getElementById('page-info').textContent = `Page ${currentPage} of ${totalPages}`; + + // Optionally hide the "Previous" button on the first page and the "Next" button on the last page + document.querySelector('#pagination-controls > button:nth-child(1)').disabled = currentPage === 1; + document.querySelector('#pagination-controls > button:nth-child(2)').disabled = currentPage === 1; + document.querySelector('#pagination-controls > button:nth-child(4)').disabled = currentPage === totalPages; + document.querySelector('#pagination-controls > button:nth-child(5)').disabled = currentPage === totalPages; + + // Make the table visible after pagination is applied + table.style.visibility = 'visible'; +} + +function changePage(direction) { + const totalPages = Math.ceil(document.querySelectorAll('#data tbody tr').length / rowsPerPage); + + if (direction === -999) { + currentPage = 1; // Go to first page + } else if (direction === 999) { + currentPage = totalPages; // Go to last page + } else if ((direction === -1 && currentPage > 1) || (direction === 1 && currentPage < totalPages)) { + currentPage += direction; // Move one step + } + + paginate(); +} diff --git a/style.css b/style.css index 00028ed..24f99c1 100644 --- a/style.css +++ b/style.css @@ -10,22 +10,18 @@ body { padding: 0 1em; } -.grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - grid-template-rows: 1fr; - grid-column-gap: 4em; - grid-row-gap: 0px; +table { + color: #000; + visibility: hidden; + width: 100%; } -table { color: #000; width: 100%; } - -table, tr, td { +table, tr, td, th { border: 1px solid #333; border-collapse: collapse; } -thead td { +thead th { background: #ccc; color: #000; font-weight: bold; @@ -40,10 +36,25 @@ tr.credit { background-color: var(--green); color: #fff; } td { padding: .15em 1em; } -h1 { } +#pagination-controls { + align-items: center; + display: flex; + justify-content: center; + gap: 1em; + margin: 1em 0; +} -.vault h2, h3, h4 { margin: 0; } +h2, h3, h4 { margin: 0; } + +.grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: 1fr; + grid-column-gap: 4em; + grid-row-gap: 0px; + margin-top: .5em; +} + +.vault { margin: 0 0 3em; } .vault h3, h4 { font-style: italic; } - -.vault { margin: 0 0 1em; } \ No newline at end of file diff --git a/vault.csv b/vault.csv deleted file mode 100644 index cf4edbe..0000000 --- a/vault.csv +++ /dev/null @@ -1,2 +0,0 @@ -User1,1663917355,Initial balance,1000000 -User2,1663917355,Initial balance,1000000