mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 16:02:11 +00:00
feat(frontend): add stat card (#2687)
* feat(frontend): add stat card * feat(frontend): fix layout
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { initCss } from '../../utils'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
import { ArrowDown } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { loadIcon } from '../icon'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
export let customCss: ComponentCustomCSS<'statcomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let resolvedConfig = initConfig(
|
||||
components['statcomponent'].initialData.configuration,
|
||||
configuration
|
||||
)
|
||||
|
||||
initOutput($worldStore, id, {})
|
||||
|
||||
let css = initCss($app.css?.statcomponent, customCss)
|
||||
|
||||
let iconComponent: any
|
||||
|
||||
$: resolvedConfig.icon && handleIcon()
|
||||
|
||||
async function handleIcon() {
|
||||
if (resolvedConfig.icon) {
|
||||
iconComponent = await loadIcon(resolvedConfig.icon)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['statcomponent'].initialData.configuration) as key (key)}
|
||||
<ResolveConfig
|
||||
{id}
|
||||
{key}
|
||||
bind:resolvedConfig={resolvedConfig[key]}
|
||||
configuration={configuration[key]}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.imagecomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#if render}
|
||||
<div class="p-6 border rounded-md h-full flex flex-row gap-6 w-full items-center">
|
||||
{#if resolvedConfig.icon && iconComponent}
|
||||
<div class="bg-blue-500 rounded-md p-2">
|
||||
<svelte:component this={iconComponent} size={24} color="white" />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="w-full">
|
||||
<div class="text-base font-normal text-primary">{resolvedConfig.title}</div>
|
||||
<div class="mt-1 flex items-baseline justify-between">
|
||||
<div class="flex items-baseline text-2xl font-semibold text-blue-600 dark:text-blue-200">
|
||||
{resolvedConfig?.value}
|
||||
</div>
|
||||
|
||||
{#if resolvedConfig?.progress !== undefined && resolvedConfig?.progress !== null && resolvedConfig?.progress !== 0}
|
||||
<div
|
||||
class={twMerge(
|
||||
'flex items-center flex-row gap-2 rounded-full px-2.5 py-0.5 text-sm font-medium',
|
||||
resolvedConfig?.progress > 0
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'
|
||||
)}
|
||||
>
|
||||
<ArrowDown
|
||||
size={16}
|
||||
class={resolvedConfig?.progress > 0 ? 'transform rotate-180' : 'transform rotate-0'}
|
||||
/>
|
||||
{resolvedConfig?.progress}%
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -63,6 +63,7 @@
|
||||
import AppCarouselList from '../../components/display/AppCarouselList.svelte'
|
||||
import AppAggridTableEe from '../../components/display/table/AppAggridTableEe.svelte'
|
||||
import AppCustomComponent from '../../components/display/AppCustomComponent.svelte'
|
||||
import AppStatCard from '../../components/display/AppStatCard.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
export let selected: boolean
|
||||
@@ -677,6 +678,13 @@
|
||||
{render}
|
||||
bind:initializing
|
||||
/>
|
||||
{:else if component.type === 'statcomponent'}
|
||||
<AppStatCard
|
||||
id={component.id}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
{render}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,8 @@ import {
|
||||
PanelLeft,
|
||||
PanelTopInactive,
|
||||
ListIcon,
|
||||
Heading1
|
||||
Heading1,
|
||||
FileBarChart
|
||||
} from 'lucide-svelte'
|
||||
import type {
|
||||
Aligned,
|
||||
@@ -162,6 +163,7 @@ export type SelectTabComponent = BaseComponent<'selecttabcomponent'>
|
||||
export type SelectStepComponent = BaseComponent<'selectstepcomponent'>
|
||||
|
||||
export type CarouselListComponent = BaseComponent<'carousellistcomponent'>
|
||||
export type StatisticCardComponent = BaseComponent<'statcomponent'>
|
||||
|
||||
export type TypedComponent =
|
||||
| DisplayComponent
|
||||
@@ -223,6 +225,7 @@ export type TypedComponent =
|
||||
| CarouselListComponent
|
||||
| PlotlyComponentV2
|
||||
| ChartJsComponentV2
|
||||
| StatisticCardComponent
|
||||
|
||||
export type AppComponent = BaseAppComponent & TypedComponent
|
||||
|
||||
@@ -2712,6 +2715,37 @@ This is a paragraph.
|
||||
}
|
||||
] as AppInputSpec<'boolean', boolean>[]
|
||||
}
|
||||
},
|
||||
statcomponent: {
|
||||
name: 'Statistic card',
|
||||
icon: FileBarChart,
|
||||
documentationLink: `${documentationBaseUrl}/statistic_card`,
|
||||
dims: '2:4-3:4' as AppComponentDimensions,
|
||||
customCss: {},
|
||||
initialData: {
|
||||
configuration: {
|
||||
title: {
|
||||
type: 'static',
|
||||
value: 'Title',
|
||||
fieldType: 'text'
|
||||
},
|
||||
value: {
|
||||
type: 'static',
|
||||
value: 'Value',
|
||||
fieldType: 'text'
|
||||
},
|
||||
progress: {
|
||||
type: 'static',
|
||||
value: 0,
|
||||
fieldType: 'number'
|
||||
},
|
||||
icon: {
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
fieldType: 'icon-select'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} as const
|
||||
|
||||
|
||||
@@ -64,7 +64,8 @@ const display: ComponentSet = {
|
||||
'displaycomponent',
|
||||
'jobidlogcomponent',
|
||||
'jobidflowstatuscomponent',
|
||||
'customcomponent'
|
||||
'customcomponent',
|
||||
'statcomponent'
|
||||
]
|
||||
} as const
|
||||
|
||||
|
||||
@@ -744,5 +744,6 @@ export const quickStyleProperties: Record<
|
||||
allTabs: [typographyGrouping, sizeGrouping],
|
||||
selectedTab: [typographyGrouping, sizeGrouping]
|
||||
},
|
||||
selectstepcomponent: {}
|
||||
selectstepcomponent: {},
|
||||
statcomponent: {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user