Add GovernmentContract component

This commit is contained in:
MuslemRahimi 2024-07-05 21:09:18 +02:00
parent a43774f6d0
commit a0ac1fb7ed
3 changed files with 267 additions and 103 deletions

View File

@ -1,54 +1,95 @@
<script lang ='ts'>
import { displayCompanyName, stockTicker, screenWidth} from '$lib/store';
import { governmentContractComponent, displayCompanyName, stockTicker, screenWidth, userRegion, getCache, setCache} from '$lib/store';
import InfoModal from '$lib/components/InfoModal.svelte';
import { Chart } from 'svelte-echarts'
import { abbreviateNumber, formatString } from "$lib/utils";
import Lazy from 'svelte-lazy';
export let data;
let isLoaded = false;
const usRegion = ['cle1','iad1','pdx1','sfo1'];
let apiURL;
userRegion.subscribe(value => {
if (usRegion.includes(value)) {
apiURL = import.meta.env.VITE_USEAST_API_URL;
} else {
apiURL = import.meta.env.VITE_EU_API_URL;
}
});
export let contractList;
let rawData = [];
let optionsData;
let avgNumberOfContracts = 0;
let displayMaxContracts = 0;
let displayYear = 'n/a';
let totalAmount;
let totalContract;
function normalizer(value) {
if (Math?.abs(value) >= 1e9) {
function normalizer(value) {
if (Math?.abs(value) >= 1e18) {
return { unit: 'Q', denominator: 1e18 };
} else if (Math?.abs(value) >= 1e12) {
return { unit: 'T', denominator: 1e12 };
} else if (Math?.abs(value) >= 1e9) {
return { unit: 'B', denominator: 1e9 };
} else if (Math?.abs(value) >= 1e6) {
return { unit: 'M', denominator: 1e6 };
} else if (Math?.abs(value) >= 1e5) {
return { unit: 'K', denominator: 1e5 };
} else if (Math?.abs(value) >= 1e4) {
return { unit: 'K', denominator: 1e4 };
} else {
return { unit: '', denominator: 1 };
}
}
}
function getPlotOptions() {
function getPlotOptions() {
let dates = [];
let valueList = [];
let amountList = [];
let numList = []
// Iterate over the data and extract required information
contractList?.forEach(item => {
rawData?.forEach(item => {
// Extract year and convert it to fiscal year format
const fiscalYear = "FY" + item?.year?.slice(2);
dates?.push(fiscalYear);
// Extract totalValue
valueList?.push(item.totalValue);
amountList?.push(item?.amount);
numList?.push(item?.numOfContracts);
});
const {unit, denominator } = normalizer(Math.max(...valueList) ?? 0)
// Calculate total number of contracts
totalContract = rawData?.reduce((sum, item) => sum + item?.numOfContracts, 0)
totalAmount = rawData?.reduce((sum, item) => sum + item?.amount, 0);
avgNumberOfContracts = Math.floor((totalContract)/rawData?.length);
const { year:yearWithMaxContracts, numOfContracts:maxContracts } = rawData?.reduce((max, contract) => contract?.numOfContracts > max?.numOfContracts ? contract : max, rawData?.at(0));
displayYear = yearWithMaxContracts;
displayMaxContracts = maxContracts
const {unit, denominator } = normalizer(Math.max(...amountList) ?? 0)
const option = {
silent: true,
tooltip: {
trigger: 'axis',
hideDelay: 100, // Set the delay in milliseconds
},
animation: $screenWidth < 640 ? false: true,
grid: {
left: $screenWidth < 640 ? '0%' : '2%',
right: $screenWidth < 640 ? '5%' : '2%',
bottom: $screenWidth < 640 ? '0%' : '2%',
left: '2%',
right: '4%',
bottom: '0%',
top: '10%',
containLabel: true
},
xAxis: {
@ -63,83 +104,193 @@ function getPlotOptions() {
},
axisLabel: {
color: '#6E7079', // Change label color to white
formatter: function (value) {
formatter: function (value, index) {
if(index % 2) {
return '$'+(value / denominator)?.toFixed(1) + unit; // Format value in millions
} else {
return ''
}
},
},
},
{
type: 'value',
splitLine: {
show: false, // Disable x-axis grid lines
},
position: 'right',
},
],
series: [
{
data: valueList,
name: '# of Contracts',
data: numList,
type: 'line',
yAxisIndex: 1,
itemStyle: {
color: '#fff' // Change bar color to white
}
},
{
name: 'Amount',
data: amountList,
type: 'bar',
itemStyle: {
color: 'rgb(255,255,255,0.8)' // Change bar color to white
}
color: '#FF9E21' // Change bar color to white
}
},
]
};
return option;
}
$: {
if($stockTicker && typeof window !== 'undefined' && contractList?.length !== 0) {
optionsData = getPlotOptions()
// Calculate total number of contracts
avgNumberOfContracts = Math.floor((contractList?.reduce((sum, contract) => sum + contract?.numOfContracts, 0))/contractList?.length);
const { year:yearWithMaxContracts, numOfContracts:maxContracts } = contractList?.reduce((max, contract) => contract?.numOfContracts > max?.numOfContracts ? contract : max, contractList?.at(0));
displayYear = yearWithMaxContracts;
displayMaxContracts = maxContracts
return option;
}
}
</script>
const getGovernmentContract = async (ticker) => {
// Get cached data for the specific tickerID
const cachedData = getCache(ticker, 'getGovernmentContract');
if (cachedData) {
rawData = cachedData;
} else {
const postData = {'ticker': ticker};
// make the POST request to the endpoint
const response = await fetch(apiURL + '/government-contract', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData)
});
rawData = (await response.json());
// Cache the data for this specific tickerID with a specific name 'getGovernmentContract'
setCache(ticker, rawData, 'getGovernmentContract');
}
if(rawData?.length !== 0) {
$governmentContractComponent = true;
} else {
$governmentContractComponent = false;
}
};
<section class="overflow-hidden text-white h-full pb-8 sm:pb-2 ">
$: {
if($stockTicker && typeof window !== 'undefined') {
isLoaded=false;
const ticker = $stockTicker
const asyncFunctions = [
getGovernmentContract(ticker)
];
Promise.all(asyncFunctions)
.then((results) => {
if(rawData?.length !== 0) {
optionsData = getPlotOptions();
}
})
.catch((error) => {
console.error('An error occurred:', error);
});
isLoaded = true;
}
}
let charNumber = 20;
$: {
if($screenWidth < 640)
{
charNumber = 20;
}
else {
charNumber =40;
}
}
</script>
<section class="overflow-hidden text-white h-full pb-8">
<main class="overflow-hidden ">
<div class="flex flex-row items-center">
<label for="governmentContractsInfo" class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold">
Government Contracts
<label for="governmentContractInfo" class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold">
US Government Contract
</label>
<InfoModal
title={"Government Contracts"}
title={"Government Contract"}
content={"Government contracts are agreements between the local government and companies for goods or services. They can be substantial revenue sources for companies, particularly in sectors like defense, technology, and infrastructure. Winning contracts can enhance a company's stability and credibility, but it often involves competitive bidding and compliance with strict regulations."}
id={"governmentContractsInfo"}
id={"governmentContractInfo"}
/>
</div>
{#if contractList?.length !== 0}
{#if data?.user?.tier === 'Pro'}
{#if isLoaded}
{#if rawData?.length !== 0}
<div class="w-full flex flex-col items-start">
<div class="text-white text-sm sm:text-[1rem] mt-1 sm:mt-3 mb-1 w-full">
Gain insights into government spending on {$displayCompanyName} and determine the annual value of significant government contracts awarded to the company.
<div class="text-white text-sm sm:text-[1rem] mt-2 mb-2 w-full">
Since 2015, {$displayCompanyName} has secured a total of {totalContract} government contracts, amassing {abbreviateNumber(totalAmount,true)} in revenue. The company has averaged {avgNumberOfContracts} contracts per year, with a peak of {displayMaxContracts} contracts in {displayYear}.
</div>
</div>
<div class="pb-2 rounded-lg bg-[#0F0F0F]">
<Lazy height={300} fadeOption={{delay: 100, duration: 500}} keep={true}>
<div class="app w-full h-[300px] ">
<div class="app w-full h-[300px] mt-5">
<Chart options={optionsData} class="chart" />
</div>
</Lazy>
<div class="w-full text-white text-sm sm:text-[1rem] mt-6">
The company averaged {avgNumberOfContracts} contracts annually, peaking at {displayMaxContracts} in {displayYear}.
</div>
{:else}
<h2 class="mt-10 mb-5 flex justify-center items-center text-3xl font-bold text-slate-700 m-auto">
No data available
<svg class="w-10 sm:w-12 inline-block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#334155" d="M18.68 12.32a4.49 4.49 0 0 0-6.36.01a4.49 4.49 0 0 0 0 6.36a4.508 4.508 0 0 0 5.57.63L21 22.39L22.39 21l-3.09-3.11c1.13-1.77.87-4.09-.62-5.57m-1.41 4.95c-.98.98-2.56.97-3.54 0c-.97-.98-.97-2.56.01-3.54c.97-.97 2.55-.97 3.53 0c.97.98.97 2.56 0 3.54M10.9 20.1a6.527 6.527 0 0 1-1.48-2.32C6.27 17.25 4 15.76 4 14v3c0 2.21 3.58 4 8 4c-.4-.26-.77-.56-1.1-.9M4 9v3c0 1.68 2.07 3.12 5 3.7v-.2c0-.93.2-1.85.58-2.69C6.34 12.3 4 10.79 4 9m8-6C7.58 3 4 4.79 4 7c0 2 3 3.68 6.85 4h.05c1.2-1.26 2.86-2 4.6-2c.91 0 1.81.19 2.64.56A3.215 3.215 0 0 0 20 7c0-2.21-3.58-4-8-4Z"/></svg>
</h2>
<div class="flex flex-row items-center justify-between mx-auto mt-5 w-full sm:w-11/12">
<div class="mt-3.5 sm:mt-0 flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center">
<div class="h-full transform -translate-x-1/2 " aria-hidden="true"></div>
<div class="w-3 h-3 bg-[#fff] border-4 box-content border-[#202020] rounded-full transform sm:-translate-x-1/2" aria-hidden="true"></div>
<span class="mt-2 sm:mt-0 text-white text-center sm:text-start text-xs sm:text-md inline-block">
# of Contracts
</span>
</div>
<div class="flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center">
<div class="h-full transform -translate-x-1/2 " aria-hidden="true"></div>
<div class="w-3 h-3 bg-[#FFAD24] border-4 box-content border-[#202020] rounded-full transform sm:-translate-x-1/2" aria-hidden="true"></div>
<span class="mt-2 sm:mt-0 text-white text-xs sm:text-md sm:font-medium inline-block">
Amount
</span>
</div>
</div>
{/if}
{:else}
<div class="flex justify-center items-center h-80">
<div class="relative">
<label class="bg-[#202020] rounded-xl h-14 w-14 flex justify-center items-center absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<span class="loading loading-spinner loading-md"></span>
</label>
</div>
</div>
{/if}
{:else}
<div class="shadow-lg shadow-bg-[#000] bg-[#202020] sm:bg-opacity-[0.5] text-sm sm:text-[1rem] rounded-md w-full p-4 min-h-24 mt-4 text-white m-auto flex justify-center items-center text-center font-semibold">
<svg class="mr-1.5 w-5 h-5 inline-block"xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#A3A3A3" d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"/></svg>
Unlock content with <a class="inline-block ml-2 text-blue-400 hover:sm:text-white" href="/pricing">Pro Subscription</a>
</div>
{/if}
</main>
@ -148,9 +299,9 @@ $: {
<style>
<style>
.app {
.app {
height: 300px;
max-width: 100%; /* Ensure chart width doesn't exceed the container */
@ -158,7 +309,7 @@ $: {
@media (max-width: 640px) {
.app {
height: 230px;
height: 210px;
}
}
@ -166,4 +317,4 @@ $: {
width: 100%;
}
</style>
</style>

View File

@ -77,6 +77,10 @@ export const failToDeliverComponent= writable(<boolean>(false));
export const borrowedShareComponent= writable(<boolean>(false));
export const impliedVolatilityComponent= writable(<boolean>(false));
export const optionsNetFlowComponent= writable(<boolean>(false));
export const governmentContractComponent= writable(<boolean>(false));
export const strategyId = writable(<string> (""));
export const articleId = writable(<string> (""));

View File

@ -3,7 +3,7 @@
import {AreaSeries, Chart, PriceLine, CandlestickSeries} from 'svelte-lightweight-charts';
import { TrackingModeExitMode } from 'lightweight-charts';
import {getCache, setCache, optionsNetFlowComponent, impliedVolatilityComponent, borrowedShareComponent, clinicalTrialComponent, optionComponent, failToDeliverComponent, marketMakerComponent, analystEstimateComponent, sentimentComponent, screenWidth, displayCompanyName, numberOfUnreadNotification, globalForm, varComponent, shareStatisticsComponent, enterpriseComponent, darkPoolComponent, retailVolumeComponent, shareholderComponent, trendAnalysisComponent, revenueSegmentationComponent, priceAnalysisComponent, fundamentalAnalysisComponent, userRegion, isCrosshairMoveActive, realtimePrice, priceIncrease, currentPortfolioPrice, currentPrice, stockTicker, isOpen, isBeforeMarketOpen, isWeekend} from '$lib/store';
import {getCache, setCache, governmentContractComponent, optionsNetFlowComponent, impliedVolatilityComponent, borrowedShareComponent, clinicalTrialComponent, optionComponent, failToDeliverComponent, marketMakerComponent, analystEstimateComponent, sentimentComponent, screenWidth, displayCompanyName, numberOfUnreadNotification, globalForm, varComponent, shareStatisticsComponent, enterpriseComponent, darkPoolComponent, retailVolumeComponent, shareholderComponent, trendAnalysisComponent, revenueSegmentationComponent, priceAnalysisComponent, fundamentalAnalysisComponent, userRegion, isCrosshairMoveActive, realtimePrice, priceIncrease, currentPortfolioPrice, currentPrice, stockTicker, isOpen, isBeforeMarketOpen, isWeekend} from '$lib/store';
import { onDestroy, onMount } from 'svelte';
import BullBearSay from '$lib/components/BullBearSay.svelte';
import CommunitySentiment from '$lib/components/CommunitySentiment.svelte';
@ -1305,6 +1305,15 @@ function changeChartType() {
</Lazy>
<Lazy>
<div class="w-full mt-10 sm:mt-5 m-auto sm:pl-6 sm:pb-6 sm:pt-6 {!$governmentContractComponent ? 'hidden' : ''}">
{#await import('$lib/components/GovernmentContract.svelte') then {default: Comp}}
<svelte:component this={Comp} data={data} />
{/await}
</div>
</Lazy>
<Lazy>
<div class="w-full mt-10 sm:mt-5 m-auto sm:pl-6 sm:pb-6 sm:pt-6 {!$enterpriseComponent ? 'hidden' : ''}">
{#await import('$lib/components/Enterprise.svelte') then {default: Comp}}