|
@@ -1,339 +1,342 @@
|
|
|
<script>
|
|
<script>
|
|
|
- import { authentication } from '../store.js';
|
|
|
|
|
- import { onMount } from 'svelte';
|
|
|
|
|
- import { fade } from 'svelte/transition';
|
|
|
|
|
-
|
|
|
|
|
- let portfolioId = undefined;
|
|
|
|
|
- let result = [];
|
|
|
|
|
- let totalValue = 0;
|
|
|
|
|
- let totalAssets = 0;
|
|
|
|
|
- let authToken;
|
|
|
|
|
- let isLoading = true;
|
|
|
|
|
- let showModal = false;
|
|
|
|
|
- let searchStockResult = [];
|
|
|
|
|
- let orderBy = "total";
|
|
|
|
|
-
|
|
|
|
|
- onMount(() => {
|
|
|
|
|
- const unsubscribe = authentication.subscribe(value => {
|
|
|
|
|
- if (value?.token) {
|
|
|
|
|
- authToken = value.token;
|
|
|
|
|
- fetchPortfolio();
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- return () => unsubscribe();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- async function fetchPortfolio() {
|
|
|
|
|
- try {
|
|
|
|
|
- const response = await fetch(
|
|
|
|
|
- `${import.meta.env.VITE_STOCKS_HOST}/api/portfolios`,
|
|
|
|
|
- {
|
|
|
|
|
- method: 'GET',
|
|
|
|
|
- headers: {
|
|
|
|
|
- Authorization: 'Bearer ' + authToken
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- if (response.ok) {
|
|
|
|
|
- await update(response.json());
|
|
|
|
|
- } else {
|
|
|
|
|
- const error = await response.json();
|
|
|
|
|
- console.error('Failed to find portfolio info:', error);
|
|
|
|
|
- alert('Failed to find portfolio info: ' + error.message);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('Failed to find portfolio info', err);
|
|
|
|
|
- alert('Failed to find portfolio info');
|
|
|
|
|
- } finally {
|
|
|
|
|
- isLoading = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async function update(response) {
|
|
|
|
|
- const portfolio = await response;
|
|
|
|
|
- if (portfolio?.length > 0) {
|
|
|
|
|
- if (orderBy === "code") {
|
|
|
|
|
- result = portfolio[0].stocks.sort((a, b) => a.code.localeCompare(b.code));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (orderBy === "name") {
|
|
|
|
|
- result = portfolio[0].stocks.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (orderBy === "total") {
|
|
|
|
|
- result = portfolio[0].stocks.sort((a, b) => a.total - b.total);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- result = portfolio[0].stocks;
|
|
|
|
|
- totalValue = portfolio[0].totalValue;
|
|
|
|
|
- totalAssets = portfolio[0].totalAssets;
|
|
|
|
|
- portfolioId = portfolio[0].id;
|
|
|
|
|
- } else {
|
|
|
|
|
- await createNewPortfolio();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async function createNewPortfolio() {
|
|
|
|
|
- try {
|
|
|
|
|
- const response = await fetch(`${import.meta.env.VITE_STOCKS_HOST}/api/portfolios`, {
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- headers: {
|
|
|
|
|
- Authorization: 'Bearer ' + authToken
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (response.status === 400) {
|
|
|
|
|
- alert("Bad request. Invalid code.");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- await fetchPortfolio();
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('Update failed', err);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async function updatePortfolio(stocks) {
|
|
|
|
|
- try {
|
|
|
|
|
- const response = await fetch(`${import.meta.env.VITE_STOCKS_HOST}/api/portfolios/${portfolioId}`, {
|
|
|
|
|
- method: 'PUT',
|
|
|
|
|
- headers: {
|
|
|
|
|
- Authorization: 'Bearer ' + authToken,
|
|
|
|
|
- 'Content-Type': 'application/json'
|
|
|
|
|
- },
|
|
|
|
|
- body: JSON.stringify({stocks})
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (response.status === 400) {
|
|
|
|
|
- alert("Bad request. Invalid code.");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- await fetchPortfolio();
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('Update failed', err);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async function searchStock(code) {
|
|
|
|
|
- if (!code) return;
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await fetch(`${import.meta.env.VITE_STOCKS_HOST}/api/stocks?q=${code}`);
|
|
|
|
|
- return await res.json();
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error("Search error:", err);
|
|
|
|
|
- return [];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async function handleSubmit(e) {
|
|
|
|
|
- e.preventDefault();
|
|
|
|
|
- const code = new FormData(e.target).get("stock_code").toUpperCase();
|
|
|
|
|
-
|
|
|
|
|
- const data = await searchStock(code);
|
|
|
|
|
- if (!data || data.length === 0) {
|
|
|
|
|
- alert("Stock not found.");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- searchStockResult = data;
|
|
|
|
|
- const alreadyInPortfolio = result.some(s => s.code === data[0]?.code);
|
|
|
|
|
-
|
|
|
|
|
- if (data.length === 1 && !alreadyInPortfolio) {
|
|
|
|
|
- await addSelectedStock(data[0]);
|
|
|
|
|
- closeOrOpenModal();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- async function addSelectedStock(newStock) {
|
|
|
|
|
- const exists = result.some(stock => stock.code === newStock.code);
|
|
|
|
|
- if (exists) return;
|
|
|
|
|
-
|
|
|
|
|
- result = [
|
|
|
|
|
- ...result,
|
|
|
|
|
- {
|
|
|
|
|
- code: newStock.code,
|
|
|
|
|
- name: newStock.name,
|
|
|
|
|
- quantity: 0,
|
|
|
|
|
- price: newStock.price,
|
|
|
|
|
- total: 0,
|
|
|
|
|
- totalPercent: 0
|
|
|
|
|
- }
|
|
|
|
|
- ];
|
|
|
|
|
-
|
|
|
|
|
- closeOrOpenModal();
|
|
|
|
|
-
|
|
|
|
|
- await updatePortfolio(result);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function remove(code) {
|
|
|
|
|
- result = result.filter(stock => stock.code !== code);
|
|
|
|
|
- updatePortfolio(result);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function formatCurrency(value) {
|
|
|
|
|
- return value.toLocaleString('en-US', {
|
|
|
|
|
- style: 'currency',
|
|
|
|
|
- currency: 'USD'
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function calculatePercentage(part, total) {
|
|
|
|
|
- return total ? Math.floor((part / total) * 10000) / 100 : 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function updateStockQuantity(e) {
|
|
|
|
|
- e.preventDefault();
|
|
|
|
|
- const form = new FormData(e.target);
|
|
|
|
|
- const code = form.get("code");
|
|
|
|
|
- const quantity = parseInt(form.get("quantity")) || 0;
|
|
|
|
|
-
|
|
|
|
|
- result = result.map(stock =>
|
|
|
|
|
- stock.code === code ? {...stock, quantity} : stock
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- updatePortfolio(result);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function closeOrOpenModal() {
|
|
|
|
|
- searchStockResult = [];
|
|
|
|
|
- showModal = !showModal;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function updateOrderBy(event) {
|
|
|
|
|
- orderBy = event.target.value;
|
|
|
|
|
- fetchPortfolio();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function handleInputChange(event) {
|
|
|
|
|
- // Update stock quantity with the new value from the input field
|
|
|
|
|
- const form = new FormData(event.target.closest('form'));
|
|
|
|
|
- const code = form.get("code");
|
|
|
|
|
- const quantity = parseInt(form.get("quantity")) || 0;
|
|
|
|
|
-
|
|
|
|
|
- // Update the stock array with the new quantity
|
|
|
|
|
- result = result.map(stock =>
|
|
|
|
|
- stock.code === code ? { ...stock, quantity } : stock
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Manually trigger form submission
|
|
|
|
|
- event.target.form.requestSubmit();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ import { authentication } from '../store.js';
|
|
|
|
|
+ import { onMount } from 'svelte';
|
|
|
|
|
+ import { fade } from 'svelte/transition';
|
|
|
|
|
+
|
|
|
|
|
+ let portfolioId = undefined;
|
|
|
|
|
+ let result = [];
|
|
|
|
|
+ let totalValue = 0;
|
|
|
|
|
+ let totalAssets = 0;
|
|
|
|
|
+ let authToken;
|
|
|
|
|
+ let isLoading = true;
|
|
|
|
|
+ let showModal = false;
|
|
|
|
|
+ let searchStockResult = [];
|
|
|
|
|
+ let orderBy = 'total';
|
|
|
|
|
+
|
|
|
|
|
+ onMount(() => {
|
|
|
|
|
+ const unsubscribe = authentication.subscribe(value => {
|
|
|
|
|
+ if (value?.token) {
|
|
|
|
|
+ authToken = value.token;
|
|
|
|
|
+ fetchPortfolio();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return () => unsubscribe();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ async function fetchPortfolio() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(
|
|
|
|
|
+ `${import.meta.env.VITE_STOCKS_HOST}/api/portfolios`,
|
|
|
|
|
+ {
|
|
|
|
|
+ method: 'GET',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ Authorization: 'Bearer ' + authToken
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ await update(response.json());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const error = await response.json();
|
|
|
|
|
+ console.error('Failed to find portfolio info:', error);
|
|
|
|
|
+ alert('Failed to find portfolio info: ' + error.message);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Failed to find portfolio info', err);
|
|
|
|
|
+ alert('Failed to find portfolio info');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ isLoading = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function update(response) {
|
|
|
|
|
+ const portfolio = await response;
|
|
|
|
|
+ if (portfolio?.length > 0) {
|
|
|
|
|
+ if (orderBy === 'code') {
|
|
|
|
|
+ result = portfolio[0].stocks.sort((a, b) => a.code.localeCompare(b.code));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (orderBy === 'name') {
|
|
|
|
|
+ result = portfolio[0].stocks.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (orderBy === 'total') {
|
|
|
|
|
+ result = portfolio[0].stocks.sort((a, b) => a.total - b.total);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ result = portfolio[0].stocks;
|
|
|
|
|
+ totalValue = portfolio[0].totalValue;
|
|
|
|
|
+ totalAssets = portfolio[0].totalAssets;
|
|
|
|
|
+ portfolioId = portfolio[0].id;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await createNewPortfolio();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function createNewPortfolio() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`${import.meta.env.VITE_STOCKS_HOST}/api/portfolios`, {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ Authorization: 'Bearer ' + authToken
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (response.status === 400) {
|
|
|
|
|
+ alert('Bad request. Invalid code.');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await fetchPortfolio();
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Update failed', err);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function updatePortfolio(stocks) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`${import.meta.env.VITE_STOCKS_HOST}/api/portfolios/${portfolioId}`, {
|
|
|
|
|
+ method: 'PUT',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ Authorization: 'Bearer ' + authToken,
|
|
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({ stocks })
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (response.status === 400) {
|
|
|
|
|
+ alert('Bad request. Invalid code.');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await fetchPortfolio();
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Update failed', err);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function searchStock(code) {
|
|
|
|
|
+ if (!code) return;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await fetch(`${import.meta.env.VITE_STOCKS_HOST}/api/stocks?q=${code}`);
|
|
|
|
|
+ return await res.json();
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Search error:', err);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function handleSubmit(e) {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ const code = new FormData(e.target).get('stock_code').toUpperCase();
|
|
|
|
|
+
|
|
|
|
|
+ const data = await searchStock(code);
|
|
|
|
|
+ if (!data || data.length === 0) {
|
|
|
|
|
+ alert('Stock not found.');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ searchStockResult = data;
|
|
|
|
|
+ const alreadyInPortfolio = result.some(s => s.code === data[0]?.code);
|
|
|
|
|
+
|
|
|
|
|
+ if (data.length === 1 && !alreadyInPortfolio) {
|
|
|
|
|
+ await addSelectedStock(data[0]);
|
|
|
|
|
+ closeOrOpenModal();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function addSelectedStock(newStock) {
|
|
|
|
|
+ const exists = result.some(stock => stock.code === newStock.code);
|
|
|
|
|
+ if (exists) return;
|
|
|
|
|
+
|
|
|
|
|
+ result = [
|
|
|
|
|
+ ...result,
|
|
|
|
|
+ {
|
|
|
|
|
+ code: newStock.code,
|
|
|
|
|
+ name: newStock.name,
|
|
|
|
|
+ quantity: 0,
|
|
|
|
|
+ price: newStock.price,
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ totalPercent: 0
|
|
|
|
|
+ }
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ closeOrOpenModal();
|
|
|
|
|
+
|
|
|
|
|
+ await updatePortfolio(result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function remove(code) {
|
|
|
|
|
+ result = result.filter(stock => stock.code !== code);
|
|
|
|
|
+ updatePortfolio(result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function formatCurrency(value) {
|
|
|
|
|
+ return value.toLocaleString('en-US', {
|
|
|
|
|
+ style: 'currency',
|
|
|
|
|
+ currency: 'USD'
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function calculatePercentage(part, total) {
|
|
|
|
|
+ return total ? Math.floor((part / total) * 10000) / 100 : 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function updateStockQuantity(e) {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ const form = new FormData(e.target);
|
|
|
|
|
+ const code = form.get('code');
|
|
|
|
|
+ const quantity = parseInt(form.get('quantity')) || 0;
|
|
|
|
|
+
|
|
|
|
|
+ result = result.map(stock =>
|
|
|
|
|
+ stock.code === code ? { ...stock, quantity } : stock
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ updatePortfolio(result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function closeOrOpenModal() {
|
|
|
|
|
+ searchStockResult = [];
|
|
|
|
|
+ showModal = !showModal;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function updateOrderBy(event) {
|
|
|
|
|
+ orderBy = event.target.value;
|
|
|
|
|
+ fetchPortfolio();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function handleInputChange(event) {
|
|
|
|
|
+ // Update stock quantity with the new value from the input field
|
|
|
|
|
+ const form = new FormData(event.target.closest('form'));
|
|
|
|
|
+ const code = form.get('code');
|
|
|
|
|
+ const quantity = parseInt(form.get('quantity')) || 0;
|
|
|
|
|
+
|
|
|
|
|
+ // Update the stock array with the new quantity
|
|
|
|
|
+ result = result.map(stock =>
|
|
|
|
|
+ stock.code === code ? { ...stock, quantity } : stock
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Manually trigger form submission
|
|
|
|
|
+ event.target.form.requestSubmit();
|
|
|
|
|
+ }
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<svelte:head>
|
|
<svelte:head>
|
|
|
- <title>Stocks</title>
|
|
|
|
|
- <meta name="description" content="About"/>
|
|
|
|
|
|
|
+ <title>Stocks</title>
|
|
|
|
|
+ <meta name="description" content="Portfolio" />
|
|
|
</svelte:head>
|
|
</svelte:head>
|
|
|
|
|
|
|
|
{#if isLoading}
|
|
{#if isLoading}
|
|
|
- <div in:fade>Loading...</div>
|
|
|
|
|
|
|
+ <div in:fade>Loading...</div>
|
|
|
{:else if portfolioId}
|
|
{:else if portfolioId}
|
|
|
- <div class="button-container">
|
|
|
|
|
- <button class="btn btn-primary btn-sm" data-toggle="modal" data-target="#exampleModal" on:click={closeOrOpenModal}>Add</button>
|
|
|
|
|
-
|
|
|
|
|
- <!-- Dropdown for ordering the list -->
|
|
|
|
|
- <select class="form-control order-select" on:change={updateOrderBy}>
|
|
|
|
|
- <option value="code">Order by Code</option>
|
|
|
|
|
- <option value="name">Order by Name</option>
|
|
|
|
|
- <option value="total" selected>Order by Total</option>
|
|
|
|
|
- </select>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- {#if showModal}
|
|
|
|
|
- <div class="modal-container">
|
|
|
|
|
- <div class="modal-content">
|
|
|
|
|
- <form on:submit|preventDefault={handleSubmit}>
|
|
|
|
|
- <div class="row">
|
|
|
|
|
- <div class="col">
|
|
|
|
|
- <input type="text" class="form-control" placeholder="stock code or name"
|
|
|
|
|
- name="stock_code"
|
|
|
|
|
- oninput="this.value = this.value.toUpperCase()"
|
|
|
|
|
- autocomplete="off" autofocus>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="col">
|
|
|
|
|
- <input type="reset" value="cancel" class="btn btn-danger" on:click={closeOrOpenModal}/>
|
|
|
|
|
- <input type="submit" value="search" class="btn btn-primary"/>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </form>
|
|
|
|
|
-
|
|
|
|
|
- {#if searchStockResult.length > 0}
|
|
|
|
|
- <div class="modal-result">
|
|
|
|
|
- <div class="card" style="width: 100%;">
|
|
|
|
|
- <ul class="list-group list-group-flush">
|
|
|
|
|
- {#each searchStockResult as result}
|
|
|
|
|
- <li class="list-group-item d-flex justify-content-between align-items-center"
|
|
|
|
|
- on:click={addSelectedStock(result)}>
|
|
|
|
|
- ({result.code}) {result.name}
|
|
|
|
|
- <button class="btn btn-primary btn-sm">+</button>
|
|
|
|
|
- </li>
|
|
|
|
|
- {/each}
|
|
|
|
|
- </ul>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- {/if}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- {/if}
|
|
|
|
|
-
|
|
|
|
|
- <div in:fade class="table-container">
|
|
|
|
|
- <table class="stock-table">
|
|
|
|
|
- <thead>
|
|
|
|
|
- <tr>
|
|
|
|
|
- <th>Total Value</th>
|
|
|
|
|
- <th>Total Assets</th>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </thead>
|
|
|
|
|
- <tbody>
|
|
|
|
|
- <tr>
|
|
|
|
|
- <td class="code">{formatCurrency(totalValue)}</td>
|
|
|
|
|
- <td class="code">{totalAssets}</td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </tbody>
|
|
|
|
|
- </table>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div in:fade class="table-container">
|
|
|
|
|
- <table class="stock-table">
|
|
|
|
|
- <thead>
|
|
|
|
|
- <tr>
|
|
|
|
|
- <th>Code</th>
|
|
|
|
|
- <th>Name</th>
|
|
|
|
|
- <th>Qty</th>
|
|
|
|
|
- <th>Price</th>
|
|
|
|
|
- <th>Total</th>
|
|
|
|
|
- <th>% of Portfolio</th>
|
|
|
|
|
- <th scope="col"></th>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </thead>
|
|
|
|
|
- <tbody>
|
|
|
|
|
- {#each result as stock}
|
|
|
|
|
- <tr>
|
|
|
|
|
- <td class="code">{stock.code}</td>
|
|
|
|
|
- <td class="name">{stock.name}</td>
|
|
|
|
|
- <td class="qty-edit">
|
|
|
|
|
- <form id="updateQuantity" on:submit|preventDefault={updateStockQuantity}>
|
|
|
|
|
- <input type="hidden" name="code" value="{stock.code}"/>
|
|
|
|
|
- <input type="number" class="qty-input" name="quantity" value="{stock.quantity}" 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" on:click={() => remove(stock.code)} title="remove"></button>
|
|
|
|
|
- </td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- {/each}
|
|
|
|
|
- </tbody>
|
|
|
|
|
- </table>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div class="button-container">
|
|
|
|
|
+ <button class="btn btn-primary btn-sm" data-toggle="modal" data-target="#exampleModal" on:click={closeOrOpenModal}>
|
|
|
|
|
+ Add
|
|
|
|
|
+ </button>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Dropdown for ordering the list -->
|
|
|
|
|
+ <select class="form-control order-select" on:change={updateOrderBy}>
|
|
|
|
|
+ <option value="code">Order by Code</option>
|
|
|
|
|
+ <option value="name">Order by Name</option>
|
|
|
|
|
+ <option value="total" selected>Order by Total</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {#if showModal}
|
|
|
|
|
+ <div class="modal-container">
|
|
|
|
|
+ <div class="modal-content">
|
|
|
|
|
+ <form on:submit|preventDefault={handleSubmit}>
|
|
|
|
|
+ <div class="row">
|
|
|
|
|
+ <div class="col">
|
|
|
|
|
+ <input type="text" class="form-control" placeholder="stock code or name"
|
|
|
|
|
+ name="stock_code"
|
|
|
|
|
+ oninput="this.value = this.value.toUpperCase()"
|
|
|
|
|
+ autocomplete="off" autofocus>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="col">
|
|
|
|
|
+ <input type="reset" value="cancel" class="btn btn-danger" on:click={closeOrOpenModal} />
|
|
|
|
|
+ <input type="submit" value="search" class="btn btn-primary" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+
|
|
|
|
|
+ {#if searchStockResult.length > 0}
|
|
|
|
|
+ <div class="modal-result">
|
|
|
|
|
+ <div class="card" style="width: 100%;">
|
|
|
|
|
+ <ul class="list-group list-group-flush">
|
|
|
|
|
+ {#each searchStockResult as result}
|
|
|
|
|
+ <li class="list-group-item d-flex justify-content-between align-items-center"
|
|
|
|
|
+ on:click={addSelectedStock(result)}>
|
|
|
|
|
+ ({result.code}) {result.name}
|
|
|
|
|
+ <button class="btn btn-primary btn-sm">+</button>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ {/each}
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {/if}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {/if}
|
|
|
|
|
+
|
|
|
|
|
+ <div in:fade class="table-container">
|
|
|
|
|
+ <table class="stock-table">
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>Total Value</th>
|
|
|
|
|
+ <th>Total Assets</th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td class="code">{formatCurrency(totalValue)}</td>
|
|
|
|
|
+ <td class="code">{totalAssets}</td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div in:fade class="table-container">
|
|
|
|
|
+ <table class="stock-table">
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>Code</th>
|
|
|
|
|
+ <th>Name</th>
|
|
|
|
|
+ <th>Qty</th>
|
|
|
|
|
+ <th>Price</th>
|
|
|
|
|
+ <th>Total</th>
|
|
|
|
|
+ <th>% of Portfolio</th>
|
|
|
|
|
+ <th scope="col"></th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ {#each result as stock}
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td class="code">{stock.code}</td>
|
|
|
|
|
+ <td class="name">{stock.name}</td>
|
|
|
|
|
+ <td class="qty-edit">
|
|
|
|
|
+ <form id="updateQuantity" on:submit|preventDefault={updateStockQuantity}>
|
|
|
|
|
+ <input type="hidden" name="code" value="{stock.code}" />
|
|
|
|
|
+ <input type="number" class="qty-input" name="quantity" value="{stock.quantity}"
|
|
|
|
|
+ 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" on:click={() => remove(stock.code)} title="remove"></button>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ {/each}
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
{:else}
|
|
{:else}
|
|
|
- <div>No portfolio data available.</div>
|
|
|
|
|
|
|
+ <div>No portfolio data available.</div>
|
|
|
{/if}
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
|