|
|
@@ -298,28 +298,34 @@
|
|
|
</svelte:head>
|
|
|
|
|
|
{#if isLoading}
|
|
|
- <div in:fade>Loading...</div>
|
|
|
+ <div in:fade class="text-center py-6 text-gray-500 dark:text-gray-300">Loading...</div>
|
|
|
{:else if portfolioId}
|
|
|
- <div class="button-container">
|
|
|
+ <div class="flex flex-wrap gap-4 mb-6 items-center">
|
|
|
<button
|
|
|
- class="btn btn-primary btn-sm"
|
|
|
- data-toggle="modal"
|
|
|
- data-target="#exampleModal"
|
|
|
+ class="bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium px-4 py-2 rounded-lg shadow"
|
|
|
on:click={openModal}
|
|
|
>
|
|
|
Add
|
|
|
</button>
|
|
|
|
|
|
- <select class="form-control order-select" on:change={updateCurrency} value={currency}>
|
|
|
+ <select
|
|
|
+ class="w-40 px-3 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-400"
|
|
|
+ on:change={updateCurrency}
|
|
|
+ bind:value={currency}
|
|
|
+ >
|
|
|
<option value="BRL">BRL</option>
|
|
|
<option value="EUR">EUR</option>
|
|
|
- <option value="USD" selected>USD</option>
|
|
|
+ <option value="USD">USD</option>
|
|
|
</select>
|
|
|
|
|
|
- <select class="form-control order-select" on:change={updateOrderBy} value="{orderBy}">
|
|
|
+ <select
|
|
|
+ class="w-52 px-3 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-400"
|
|
|
+ on:change={updateOrderBy}
|
|
|
+ bind:value={orderBy}
|
|
|
+ >
|
|
|
<option value="code">Order by Code</option>
|
|
|
<option value="name">Order by Name</option>
|
|
|
- <option value="total" selected>Order by Total</option>
|
|
|
+ <option value="total">Order by Total</option>
|
|
|
<option value="weight">Order by Weight</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
@@ -334,7 +340,6 @@
|
|
|
return;
|
|
|
}
|
|
|
searchStockResult = data;
|
|
|
-
|
|
|
const alreadyInPortfolio = result.some((s) => s.code === data[0]?.code);
|
|
|
if (data.length === 1 && !alreadyInPortfolio) {
|
|
|
await addSelectedStock(data[0]);
|
|
|
@@ -349,78 +354,68 @@
|
|
|
/>
|
|
|
|
|
|
{#if showDeleteConfirm}
|
|
|
- <div class="modal-confirm">
|
|
|
- <div class="modal-actions">
|
|
|
- <button class="btn btn-danger" on:click={confirmDeleteAction}>Confirm deletion</button>
|
|
|
- <button class="btn btn-cancel" on:click={cancelDelete}>Cancel</button>
|
|
|
+ <div class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white dark:bg-gray-800 p-6 rounded-xl shadow-lg z-50 text-center">
|
|
|
+ <div class="flex justify-center gap-4">
|
|
|
+ <button class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg" on:click={confirmDeleteAction}>Confirm deletion</button>
|
|
|
+ <button class="bg-gray-300 hover:bg-gray-400 text-gray-800 px-4 py-2 rounded-lg" on:click={cancelDelete}>Cancel</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
{/if}
|
|
|
|
|
|
- <div in:fade class="table-container">
|
|
|
- <table class="stock-table">
|
|
|
- <thead>
|
|
|
+ <div in:fade class="overflow-x-auto mt-6 rounded-xl shadow">
|
|
|
+ <table class="min-w-full bg-white dark:bg-gray-900 text-sm">
|
|
|
+ <thead class="bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200">
|
|
|
<tr>
|
|
|
- <th>Total Value</th>
|
|
|
- <th>Total Assets</th>
|
|
|
+ <th class="px-4 py-3 text-left font-semibold">Total Value</th>
|
|
|
+ <th class="px-4 py-3 text-left font-semibold">Total Assets</th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody>
|
|
|
- <tr>
|
|
|
- <td class="code">{formatCurrency(totalValue)}</td>
|
|
|
- <td class="code">{totalAssets}</td>
|
|
|
+ <tr class="border-t border-gray-200 dark:border-gray-700">
|
|
|
+ <td class="px-4 py-3 font-semibold text-gray-800 dark:text-gray-100">{formatCurrency(totalValue)}</td>
|
|
|
+ <td class="px-4 py-3 font-semibold text-gray-800 dark:text-gray-100">{totalAssets}</td>
|
|
|
</tr>
|
|
|
</tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
|
|
|
- <div in:fade class="table-container">
|
|
|
- <table class="stock-table">
|
|
|
- <thead>
|
|
|
+ <div in:fade class="overflow-x-auto mt-6 rounded-xl shadow">
|
|
|
+ <table class="min-w-full bg-white dark:bg-gray-900 text-sm">
|
|
|
+ <thead class="bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200">
|
|
|
<tr>
|
|
|
- <th>Code</th>
|
|
|
- <th>Name</th>
|
|
|
- <th>Qty</th>
|
|
|
- <th>Price</th>
|
|
|
- <th>Total</th>
|
|
|
- <th>% of Portfolio</th>
|
|
|
- <th scope="col"></th>
|
|
|
+ <th class="px-4 py-3">Code</th>
|
|
|
+ <th class="px-4 py-3">Name</th>
|
|
|
+ <th class="px-4 py-3">Qty</th>
|
|
|
+ <th class="px-4 py-3">Price</th>
|
|
|
+ <th class="px-4 py-3">Total</th>
|
|
|
+ <th class="px-4 py-3">% of Portfolio</th>
|
|
|
+ <th class="px-4 py-3"></th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody>
|
|
|
{#each result as stock}
|
|
|
- <tr>
|
|
|
- <td class="code">
|
|
|
- <div class="col-code" title="{stock.code}" on:click={openStock(stock.code)}>
|
|
|
- <img
|
|
|
- src={`https://flagcdn.com/w20/${getFlag(stock.code)}.png`}
|
|
|
- alt="{getFlag(stock.code)} flag"
|
|
|
- class="flag-icon"
|
|
|
- />
|
|
|
+ <tr class="odd:bg-gray-50 even:bg-gray-100 dark:odd:bg-gray-800 dark:even:bg-gray-700 hover:bg-blue-50 dark:hover:bg-blue-900 transition">
|
|
|
+ <td class="px-4 py-2 font-mono font-semibold text-blue-700 dark:text-blue-300 cursor-pointer" title={stock.code} on:click={() => openStock(stock.code)}>
|
|
|
+ <div class="inline-flex items-center gap-2">
|
|
|
+ <img src={`https://flagcdn.com/w20/${getFlag(stock.code)}.png`} alt="{getFlag(stock.code)} flag" class="w-5 h-auto" />
|
|
|
{formatCode(stock.code)}
|
|
|
</div>
|
|
|
</td>
|
|
|
- <td class="name">{stock.name}</td>
|
|
|
- <td class="qty-edit">
|
|
|
- <form id="updateQuantity" on:submit|preventDefault={updateStockQuantity}>
|
|
|
+ <td class="px-4 py-2 text-gray-700 dark:text-gray-300">{stock.name}</td>
|
|
|
+ <td class="px-4 py-2">
|
|
|
+ <form id="updateQuantity" on:submit|preventDefault={updateStockQuantity} class="flex items-center">
|
|
|
<input type="hidden" name="code" value={stock.code} />
|
|
|
- <input
|
|
|
- type="number"
|
|
|
- class="qty-input"
|
|
|
- name="quantity"
|
|
|
- min="0"
|
|
|
- value={stock.quantity}
|
|
|
- on:input={handleInputChange}
|
|
|
- />
|
|
|
+ <input type="number" name="quantity" min="0" value={stock.quantity}
|
|
|
+ class="w-16 px-2 py-1 text-right border rounded-lg text-sm text-gray-800 dark:text-gray-100 bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-300" on:input={handleInputChange} />
|
|
|
</form>
|
|
|
</td>
|
|
|
- <td class="price">{formatCurrency(stock.price)}</td>
|
|
|
- <td class="total">{formatCurrency(stock.total)}</td>
|
|
|
- <td class="percent">{calculatePercentage(stock.total, totalValue)}%</td>
|
|
|
- <td>
|
|
|
- <button class="remove-btn" aria-label="Delete" on:click={() => confirmDelete(stock.code)} title="remove">
|
|
|
- <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round"
|
|
|
- stroke-linejoin="round">
|
|
|
+ <td class="px-4 py-2 font-semibold text-green-600 dark:text-green-400">{formatCurrency(stock.price)}</td>
|
|
|
+ <td class="px-4 py-2 font-semibold text-gray-800 dark:text-gray-200">{formatCurrency(stock.total)}</td>
|
|
|
+ <td class="px-4 py-2 text-blue-600 dark:text-blue-400">{calculatePercentage(stock.total, totalValue)}%</td>
|
|
|
+ <td class="px-4 py-2 text-right">
|
|
|
+ <button on:click={() => confirmDelete(stock.code)} aria-label="Delete" title="remove"
|
|
|
+ class="bg-red-600 hover:bg-red-700 text-white rounded-full p-2 shadow focus:outline-none focus:ring-2 focus:ring-red-400">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4">
|
|
|
<polyline points="3 6 5 6 21 6"></polyline>
|
|
|
<path d="M19 6l-1 14H6L5 6"></path>
|
|
|
<path d="M10 11v6"></path>
|
|
|
@@ -436,300 +431,11 @@
|
|
|
</div>
|
|
|
|
|
|
{#if hasChanges}
|
|
|
- <button class="btn btn-primary apply-changes-btn" on:click={applyChanges}>
|
|
|
+ <button class="fixed bottom-5 right-5 bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-lg shadow-lg z-50" on:click={applyChanges}>
|
|
|
Apply Changes
|
|
|
</button>
|
|
|
{/if}
|
|
|
{:else}
|
|
|
- <div>No portfolio data available.</div>
|
|
|
+ <div class="text-gray-500 dark:text-gray-300 text-center py-6">No portfolio data available.</div>
|
|
|
{/if}
|
|
|
|
|
|
-<style>
|
|
|
- .apply-changes-btn {
|
|
|
- position: fixed;
|
|
|
- bottom: 20px;
|
|
|
- right: 20px;
|
|
|
- z-index: 100;
|
|
|
- }
|
|
|
-
|
|
|
- .table-container {
|
|
|
- margin-top: 2rem;
|
|
|
- overflow-x: auto;
|
|
|
- border-radius: 12px;
|
|
|
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
|
- }
|
|
|
-
|
|
|
- .stock-table {
|
|
|
- width: 100%;
|
|
|
- border-collapse: collapse;
|
|
|
- font-family: system-ui, sans-serif;
|
|
|
- background-color: #fff;
|
|
|
- border-radius: 12px;
|
|
|
- overflow: hidden;
|
|
|
- min-width: 600px;
|
|
|
- }
|
|
|
-
|
|
|
- th,
|
|
|
- td {
|
|
|
- padding: 0.5rem 1rem;
|
|
|
- text-align: left;
|
|
|
- white-space: nowrap;
|
|
|
- }
|
|
|
-
|
|
|
- thead {
|
|
|
- background-color: #f7f7f7;
|
|
|
- border-bottom: 2px solid #e0e0e0;
|
|
|
- }
|
|
|
-
|
|
|
- th {
|
|
|
- font-weight: 600;
|
|
|
- font-size: 0.95rem;
|
|
|
- color: #333;
|
|
|
- }
|
|
|
-
|
|
|
- tbody tr:nth-child(odd) {
|
|
|
- background-color: #fafafa;
|
|
|
- }
|
|
|
-
|
|
|
- tbody tr:nth-child(even) {
|
|
|
- background-color: #f0f4f8;
|
|
|
- }
|
|
|
-
|
|
|
- tbody tr:hover {
|
|
|
- background-color: #e1ecf4;
|
|
|
- }
|
|
|
-
|
|
|
- td {
|
|
|
- font-size: 0.95rem;
|
|
|
- color: #555;
|
|
|
- border-bottom: 1px solid #eee;
|
|
|
- }
|
|
|
-
|
|
|
- .code {
|
|
|
- font-weight: 600;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- .col-code {
|
|
|
- display: inline-block;
|
|
|
- background-color: #e0f2ff;
|
|
|
- color: #0369a1;
|
|
|
- font-weight: 600;
|
|
|
- font-family: monospace;
|
|
|
- padding: 4px 10px;
|
|
|
- border-radius: 9999px;
|
|
|
- font-size: 0.875rem;
|
|
|
- cursor: default;
|
|
|
- }
|
|
|
-
|
|
|
- .col-code:hover {
|
|
|
- background-color: #035f8c;
|
|
|
- color: #ffffff;
|
|
|
- }
|
|
|
-
|
|
|
- .name {
|
|
|
- color: #7f8c8d;
|
|
|
- }
|
|
|
-
|
|
|
- .qty-edit {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 0.5rem;
|
|
|
- }
|
|
|
-
|
|
|
- .qty-input {
|
|
|
- width: 60px;
|
|
|
- padding: 0.4rem 0.5rem;
|
|
|
- font-size: 0.9rem;
|
|
|
- border: 1px solid #ccc;
|
|
|
- border-radius: 6px;
|
|
|
- text-align: right;
|
|
|
- background-color: #fff;
|
|
|
- color: #333;
|
|
|
- transition: border-color 0.2s ease;
|
|
|
- }
|
|
|
-
|
|
|
- .qty-input:focus {
|
|
|
- outline: none;
|
|
|
- border-color: #2980b9;
|
|
|
- }
|
|
|
-
|
|
|
- .remove-btn {
|
|
|
- height: 32px;
|
|
|
- width: 32px;
|
|
|
- background-color: #e74c3c;
|
|
|
- border: none;
|
|
|
- border-radius: 50%;
|
|
|
- display: inline-flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- cursor: pointer;
|
|
|
- transition: background-color 0.2s ease, transform 0.1s ease;
|
|
|
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .remove-btn svg {
|
|
|
- width: 16px;
|
|
|
- height: 16px;
|
|
|
- }
|
|
|
-
|
|
|
- .remove-btn:hover {
|
|
|
- background-color: #c0392b;
|
|
|
- transform: scale(1.05);
|
|
|
- }
|
|
|
-
|
|
|
- .remove-btn:active {
|
|
|
- transform: scale(0.95);
|
|
|
- }
|
|
|
-
|
|
|
- .remove-btn:focus {
|
|
|
- outline: none;
|
|
|
- box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.4);
|
|
|
- }
|
|
|
-
|
|
|
- .btn-cancel {
|
|
|
- background-color: #bdc3c7;
|
|
|
- color: #2c3e50;
|
|
|
- border: none;
|
|
|
- border-radius: 8px;
|
|
|
- padding: 0.6rem 1.2rem;
|
|
|
- font-size: 1rem;
|
|
|
- cursor: pointer;
|
|
|
- transition: background-color 0.3s ease;
|
|
|
- }
|
|
|
-
|
|
|
- .btn-cancel:hover {
|
|
|
- background-color: #aeb6bf;
|
|
|
- }
|
|
|
-
|
|
|
- .btn-cancel:focus {
|
|
|
- outline: none;
|
|
|
- box-shadow: 0 0 0 3px rgba(189, 195, 199, 0.5);
|
|
|
- }
|
|
|
-
|
|
|
- .modal-confirm {
|
|
|
- position: fixed;
|
|
|
- top: 50%;
|
|
|
- left: 50%;
|
|
|
- transform: translate(-50%, -50%);
|
|
|
- background: white;
|
|
|
- padding: 2rem;
|
|
|
- border-radius: 12px;
|
|
|
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
|
|
- z-index: 1000;
|
|
|
- text-align: center;
|
|
|
- max-width: 90%;
|
|
|
- width: 400px;
|
|
|
- animation: fadeIn 0.2s ease;
|
|
|
- }
|
|
|
-
|
|
|
- .modal-actions {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- gap: 1rem;
|
|
|
- }
|
|
|
-
|
|
|
- .btn-danger {
|
|
|
- background-color: #e74c3c;
|
|
|
- color: white;
|
|
|
- }
|
|
|
-
|
|
|
- .btn-danger:hover {
|
|
|
- background-color: #c0392b;
|
|
|
- }
|
|
|
-
|
|
|
- .price {
|
|
|
- color: #27ae60;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .total {
|
|
|
- font-weight: 500;
|
|
|
- color: #34495e;
|
|
|
- }
|
|
|
-
|
|
|
- .percent {
|
|
|
- color: #2980b9;
|
|
|
- }
|
|
|
-
|
|
|
- @media (max-width: 768px) {
|
|
|
- input[type='number'] {
|
|
|
- font-size: 0.9rem;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @media (max-width: 768px) {
|
|
|
- .stock-table {
|
|
|
- font-size: 0.875rem;
|
|
|
- }
|
|
|
-
|
|
|
- th,
|
|
|
- td {
|
|
|
- padding: 0.75rem 1rem;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .button-container {
|
|
|
- display: flex;
|
|
|
- gap: 10px;
|
|
|
- margin-bottom: 1rem;
|
|
|
- align-items: center;
|
|
|
- }
|
|
|
-
|
|
|
- .order-select {
|
|
|
- width: 200px;
|
|
|
- padding: 0.5rem;
|
|
|
- border-radius: 8px;
|
|
|
- border: 1px solid #ddd;
|
|
|
- font-size: 1rem;
|
|
|
- background-color: #f9f9f9;
|
|
|
- transition: border-color 0.3s ease;
|
|
|
- }
|
|
|
-
|
|
|
- .order-select:focus {
|
|
|
- border-color: #2980b9;
|
|
|
- outline: none;
|
|
|
- }
|
|
|
-
|
|
|
- input[type='number'] {
|
|
|
- font-size: 0.8rem;
|
|
|
- border-radius: 8px;
|
|
|
- border: 1px solid #ddd;
|
|
|
- background-color: #f9f9f9;
|
|
|
- color: #333;
|
|
|
- transition: border-color 0.3s ease;
|
|
|
- }
|
|
|
-
|
|
|
- input[type='number']:focus {
|
|
|
- border-color: #2980b9;
|
|
|
- outline: none;
|
|
|
- }
|
|
|
-
|
|
|
- .btn {
|
|
|
- padding: 0.6rem 1.2rem;
|
|
|
- font-size: 1rem;
|
|
|
- border-radius: 8px;
|
|
|
- border: none;
|
|
|
- cursor: pointer;
|
|
|
- transition: background-color 0.3s ease;
|
|
|
- }
|
|
|
-
|
|
|
- .btn-primary {
|
|
|
- background-color: #3498db;
|
|
|
- color: white;
|
|
|
- }
|
|
|
-
|
|
|
- .btn-primary:hover {
|
|
|
- background-color: #2980b9;
|
|
|
- }
|
|
|
-
|
|
|
- @keyframes fadeIn {
|
|
|
- from {
|
|
|
- opacity: 0;
|
|
|
- }
|
|
|
- to {
|
|
|
- opacity: 1;
|
|
|
- }
|
|
|
- }
|
|
|
-</style>
|