fix: type errors

This commit is contained in:
Danny White committed 2025-08-22 12:45:02 +10:00
1 parent dceef509a0
commit abfe84fa46
26 files changed
+6 -1223

No files matched your search

@@ -1,34 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
// Generate SQL query for team count chart
function generatePersonAgeSQL(activeFilters) {
const whereClauses = []
if (activeFilters.role !== 'unset') {
whereClauses.push(`role = '${activeFilters.role}'`)
}
if (activeFilters.headquarters !== 'unset') {
whereClauses.push(`headquarters = '${activeFilters.headquarters}'`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `SELECT
person_age,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY person_age
ORDER BY person_age ASC`
}
export function PersonAgeChart() {
return (
<GenericChartWithQuery
title="What is your age?"
targetColumn="person_age"
filterColumns={['role', 'headquarters']}
generateSQLQuery={generatePersonAgeSQL}
/>
)
}
@@ -1,39 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateBackendStackSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.headquarters !== 'unset') {
whereClauses.push(`headquarters = '${activeFilters.headquarters}'`)
}
if (activeFilters.industry_normalized !== 'unset') {
whereClauses.push(`industry_normalized = '${activeFilters.industry_normalized}'`)
}
if (activeFilters.currently_monetizing !== 'unset') {
whereClauses.push(`currently_monetizing = '${activeFilters.currently_monetizing}'`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `
SELECT
unnest(backend_stack) AS technology,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY technology
ORDER BY total DESC;
`
}
export function BackendStackChart() {
return (
<GenericChartWithQuery
title="What is your startup's backend stack?"
targetColumn="backend_stack"
filterColumns={['headquarters', 'industry_normalized', 'currently_monetizing']}
generateSQLQuery={generateBackendStackSQL}
/>
)
}
@@ -1,7 +1,7 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
// Generate SQL query for team count chart
function generateBiggestChallengeSQL(activeFilters) {
function generateBiggestChallengeSQL(activeFilters: Record<string, string>) {
const whereClauses = []
// Always filter out NULL values
@@ -1,41 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateCloudProvidersSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.currently_monetizing !== 'unset') {
whereClauses.push(`currently_monetizing = '${activeFilters.currently_monetizing}'`)
}
if (activeFilters.money_raised !== 'unset') {
whereClauses.push(`money_raised = '${activeFilters.money_raised}'`)
}
if (activeFilters.backend_stack !== 'unset') {
whereClauses.push(`'${activeFilters.backend_stack}' = ANY(backend_stack)`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `WITH expanded AS (
SELECT unnest(cloud_providers) AS technology
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
)
SELECT
technology,
COUNT(*) AS total
FROM expanded
GROUP BY technology
ORDER BY total DESC;`
}
export function CloudProvidersChart() {
return (
<GenericChartWithQuery
title="Which cloud provider(s) is your startup using?"
targetColumn="observability"
filterColumns={['currently_monetizing', 'money_raised', 'backend_stack']}
generateSQLQuery={generateCloudProvidersSQL}
/>
)
}
@@ -1,48 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateDataWarehousesSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.headquarters !== 'unset') {
whereClauses.push(`headquarters = '${activeFilters.headquarters}'`)
}
if (activeFilters.industry_normalized !== 'unset') {
whereClauses.push(`industry_normalized = '${activeFilters.industry_normalized}'`)
}
if (activeFilters.person_age !== 'unset') {
whereClauses.push(`person_age = '${activeFilters.person_age}'`)
}
if (activeFilters.frontend_stack !== 'unset') {
whereClauses.push(`'${activeFilters.frontend_stack}' = ANY(frontend_stack)`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `
WITH expanded AS (
SELECT unnest(data_warehouses) AS technology
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
)
SELECT
technology,
COUNT(*) AS total
FROM expanded
WHERE technology NOT IN ('We don''t use a data warehouse', 'Not sure')
GROUP BY technology
ORDER BY total DESC;
`
}
export function DataWarehousesChart() {
return (
<GenericChartWithQuery
title="Which data warehouse(s) is your startup using? (Filtered)"
targetColumn="data_warehouses"
filterColumns={['headquarters', 'industry_normalized', 'person_age', 'frontend_stack']}
generateSQLQuery={generateDataWarehousesSQL}
/>
)
}
@@ -1,101 +0,0 @@
'use client'
// import { Bar, BarChart, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
// import { ChartContainer } from 'ui'
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis, Cell } from 'recharts'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
export const description = 'A bar chart with a custom label'
// const chartData = [
// { month: "January", desktop: 186, mobile: 80 },
// { month: "February", desktop: 305, mobile: 200 },
// { month: "March", desktop: 237, mobile: 120 },
// { month: "April", desktop: 73, mobile: 190 },
// { month: "May", desktop: 209, mobile: 130 },
// { month: "June", desktop: 214, mobile: 140 },
// ]
const chartData = [
{ label: '1–10', value: 91.1 },
{ label: '11–50', value: 6.3 },
{ label: '51–100', value: 1.2 },
{ label: '101–250', value: 0.8 },
{ label: '250+', value: 0.6 },
]
const chartConfig = {
value: {
label: 'Value',
color: 'hsl(var(--brand-default))',
},
label: {
color: '#ff0000',
},
} satisfies ChartConfig
export function FullTimeEmployees() {
return (
<Card>
<CardHeader>
<CardTitle>How many full-time employees does your startup have?</CardTitle>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig}>
<BarChart
accessibilityLayer
data={chartData}
layout="vertical"
margin={{
right: 0,
}}
>
<CartesianGrid horizontal={false} />
<YAxis
dataKey="label"
type="category"
tickLine={false}
tickMargin={64}
axisLine={false}
// tickFormatter={(value) => value.slice(0, 3)}
hide={false} // Show the YAxis
width={64} // Adjust width as needed for your labels
tick={{
// fill: 'hsl(var(--foreground-lighter))', // Text color TODO not working
className: 'text-foreground-lighter', // Also not accessible
fontSize: 12,
textAnchor: 'start', // Left align
dx: 0, // Horizontal offset
}}
/>
<XAxis dataKey="value" type="number" hide />
{/* <ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" />}
/> */}
<Bar dataKey="value" layout="vertical" radius={4}>
{/* To set per-bar colors in Recharts, `fill` should be applied to the Cell child */}
{chartData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={index === 0 ? 'hsl(var(--brand-default))' : 'hsl(var(--brand-300))'}
/>
))}
<LabelList
dataKey="value"
position="right"
offset={8}
className="fill-foreground"
fontSize={12}
formatter={(value: number) => `${value}%`}
/>
</Bar>
</BarChart>
</ChartContainer>
</CardContent>
</Card>
)
}
@@ -1,6 +1,6 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateFundingStageSQL(activeFilters) {
function generateFundingStageSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.headquarters !== 'unset') {
@@ -1,7 +1,7 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
// Generate SQL query for team count chart
function generateIndustrySQL(activeFilters) {
function generateIndustrySQL(activeFilters: Record<string, string>) {
const whereClauses = []
// Always filter out NULL values
@@ -1,34 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateMonetizationSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.headquarters !== 'unset') {
whereClauses.push(`headquarters = '${activeFilters.headquarters}'`)
}
if (activeFilters.money_raised !== 'unset') {
whereClauses.push(`money_raised = '${activeFilters.money_raised}'`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `SELECT
currently_monetizing,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY currently_monetizing
ORDER BY total DESC;`
}
export function MonetizationChart() {
return (
<GenericChartWithQuery
title="Is your startup monetizing today?"
targetColumn="currently_monetizing"
filterColumns={['headquarters', 'money_raised']}
generateSQLQuery={generateMonetizationSQL}
chartType="pie"
/>
)
}
@@ -1,43 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateMustHaveDevToolsSQL(activeFilters: Record<string, string>) {
const whereClauses = []
// Always filter out NULL values
whereClauses.push(`must_have_dev_tools IS NOT NULL`)
whereClauses.push(`must_have_dev_tools != ''`)
if (activeFilters.currently_monetizing !== 'unset') {
whereClauses.push(`currently_monetizing = '${activeFilters.currently_monetizing}'`)
}
if (activeFilters.money_raised !== 'unset') {
whereClauses.push(`money_raised = '${activeFilters.money_raised}'`)
}
if (activeFilters.backend_stack !== 'unset') {
whereClauses.push(`'${activeFilters.backend_stack}' = ANY(backend_stack)`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `
SELECT
must_have_dev_tools,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY must_have_dev_tools
ORDER BY total DESC
LIMIT 100;`
}
export function MustHaveDevToolsChart() {
return (
<GenericChartWithQuery
title="What are your must-have dev tools?"
targetColumn="must_have_dev_tools"
filterColumns={['currently_monetizing', 'money_raised', 'backend_stack']}
generateSQLQuery={generateMustHaveDevToolsSQL}
/>
)
}
@@ -1,45 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateNewslettersPaidForSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.headquarters !== 'unset') {
whereClauses.push(`headquarters = '${activeFilters.headquarters}'`)
}
if (activeFilters.industry_normalized !== 'unset') {
whereClauses.push(`industry_normalized = '${activeFilters.industry_normalized}'`)
}
if (activeFilters.currently_monetizing !== 'unset') {
whereClauses.push(`currently_monetizing = '${activeFilters.currently_monetizing}'`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `
SELECT
unnest(newsletters_paid_for) AS technology,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY technology
UNION ALL
SELECT
'None of the above' AS technology,
COUNT(*) AS total
FROM responses_2025
WHERE newsletters_paid_for = '{}'
ORDER BY total DESC;
`
}
export function NewslettersPaidForChart() {
return (
<GenericChartWithQuery
title="Which newsletters do you currently pay for subscriptions?"
targetColumn="newsletters_paid_for"
filterColumns={['headquarters', 'industry_normalized', 'currently_monetizing']}
generateSQLQuery={generateNewslettersPaidForSQL}
/>
)
}
@@ -1,38 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateObservabilitySQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.currently_monetizing !== 'unset') {
whereClauses.push(`currently_monetizing = '${activeFilters.currently_monetizing}'`)
}
if (activeFilters.money_raised !== 'unset') {
whereClauses.push(`money_raised = '${activeFilters.money_raised}'`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `WITH expanded AS (
SELECT unnest(observability) AS technology
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
)
SELECT
technology,
COUNT(*) AS total
FROM expanded
WHERE technology NOT IN ('We don''t use observability tools yet', 'Not yet')
GROUP BY technology
ORDER BY total DESC;`
}
export function ObservabilityChart() {
return (
<GenericChartWithQuery
title="Which tools are your startup currently using for monitoring and observability? (Filtered)"
targetColumn="observability"
filterColumns={['currently_monetizing', 'money_raised']}
generateSQLQuery={generateObservabilitySQL}
/>
)
}
@@ -1,39 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateOpenTelemetrySQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.currently_monetizing !== 'unset') {
whereClauses.push(`currently_monetizing = '${activeFilters.currently_monetizing}'`)
}
if (activeFilters.money_raised !== 'unset') {
whereClauses.push(`money_raised = '${activeFilters.money_raised}'`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `SELECT
use_opentelemetry,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY use_opentelemetry
ORDER BY
CASE use_opentelemetry
WHEN 'Yes' THEN 1
WHEN 'Not yet, but planning to' THEN 2
WHEN 'No' THEN 3
END;`
}
export function OpenTelemetryChart() {
return (
<GenericChartWithQuery
title="Do you use OpenTelemetry in your observability stack?"
targetColumn="use_opentelemetry"
filterColumns={['currently_monetizing', 'money_raised']}
generateSQLQuery={generateOpenTelemetrySQL}
chartType="pie"
/>
)
}
@@ -1,50 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generatePivotFreqSQL(activeFilters: Record<string, string>) {
const whereClauses = []
// if (activeFilters.headquarters !== 'unset') {
// whereClauses.push(`headquarters = '${activeFilters.headquarters}'`)
// }
// if (activeFilters.currently_monetizing !== 'unset') {
// whereClauses.push(`currently_monetizing = '${activeFilters.currently_monetizing}'`)
// }
if (activeFilters.person_age !== 'unset') {
whereClauses.push(`person_age = '${activeFilters.person_age}'`)
}
if (activeFilters.previous_company !== 'unset') {
// Convert string to boolean for the SQL query
const previousCompanyBool = activeFilters.previous_company === 'true'
whereClauses.push(`previous_company = ${previousCompanyBool}`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `SELECT
pivots_before_current,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY pivots_before_current
ORDER BY
CASE pivots_before_current
WHEN 'Never' THEN 1
WHEN 'Once' THEN 2
WHEN 'Twice' THEN 3
WHEN 'More than twice' THEN 4
END;`
}
export function PivotFreqChart() {
return (
<GenericChartWithQuery
title="How many times did your startup have to pivot before arriving at the current idea?"
targetColumn="pivots_before_current"
// filterColumns={['headquarters', 'currently_monetizing', 'person_age', 'previous_company']}
filterColumns={['person_age', 'previous_company']}
generateSQLQuery={generatePivotFreqSQL}
/>
)
}
@@ -1,46 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generatePodcastsListenedToSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.headquarters !== 'unset') {
whereClauses.push(`headquarters = '${activeFilters.headquarters}'`)
}
if (activeFilters.industry_normalized !== 'unset') {
whereClauses.push(`industry_normalized = '${activeFilters.industry_normalized}'`)
}
if (activeFilters.currently_monetizing !== 'unset') {
whereClauses.push(`currently_monetizing = '${activeFilters.currently_monetizing}'`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `SELECT technology, COUNT(*) AS total
FROM (
SELECT
CASE
WHEN podcasts_listened_to = '{}' THEN 'None'
WHEN podcast = 'I don''t listen to podcasts regularly' THEN 'None'
ELSE podcast
END AS technology
FROM responses_2025
CROSS JOIN LATERAL unnest(
CASE WHEN podcasts_listened_to = '{}' THEN ARRAY['None'] ELSE podcasts_listened_to END
) AS podcast${whereClause ? '\n' + whereClause : ''}
) t
GROUP BY technology
ORDER BY total DESC;`
}
export function PodcastsListenedToChart() {
return (
<GenericChartWithQuery
title="Which of these podcasts do you listen to regularly?"
targetColumn="podcasts_listened_to"
filterColumns={['headquarters', 'industry_normalized', 'currently_monetizing']}
generateSQLQuery={generatePodcastsListenedToSQL}
/>
)
}
@@ -1,42 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generatePreviousCompanySQL(activeFilters: Record<string, string>) {
const whereClauses = []
// Always filter out NULL values
whereClauses.push(`previous_company IS NOT NULL`)
if (activeFilters.person_age !== 'unset') {
whereClauses.push(`person_age = '${activeFilters.person_age}'`)
}
if (activeFilters.money_raised !== 'unset') {
whereClauses.push(`money_raised = '${activeFilters.money_raised}'`)
}
const whereClause = `WHERE ${whereClauses.join('\n AND ')}`
return `SELECT
CASE
WHEN previous_company = true THEN 'Yes'
WHEN previous_company = false THEN 'No'
-- ELSE 'Unknown'
END AS previous_company,
COUNT(*) AS total
FROM responses_2025
${whereClause}
GROUP BY previous_company
ORDER BY total DESC;`
}
export function PreviousCompanyChart() {
return (
<GenericChartWithQuery
title="Had you started a company before your current startup?"
targetColumn="previous_company"
filterColumns={['person_age', 'money_raised']}
generateSQLQuery={generatePreviousCompanySQL}
chartType="pie"
/>
)
}
@@ -1,31 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generatePricingSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.team_count !== 'unset') {
whereClauses.push(`team_count = '${activeFilters.team_count}'`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `
SELECT
unnest(pricing) AS pricing_model,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY pricing_model
ORDER BY total DESC;
`
}
export function PricingChart() {
return (
<GenericChartWithQuery
title="TK: Pricing question"
targetColumn="pricing"
filterColumns={['team_count']}
generateSQLQuery={generatePricingSQL}
/>
)
}
@@ -1,43 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateProblemBeingSolvedSQL(activeFilters: Record<string, string>) {
const whereClauses = []
// Always filter out NULL values
whereClauses.push(`problem_solving IS NOT NULL`)
whereClauses.push(`problem_solving != ''`)
if (activeFilters.currently_monetizing !== 'unset') {
whereClauses.push(`currently_monetizing = '${activeFilters.currently_monetizing}'`)
}
if (activeFilters.money_raised !== 'unset') {
whereClauses.push(`money_raised = '${activeFilters.money_raised}'`)
}
if (activeFilters.backend_stack !== 'unset') {
whereClauses.push(`'${activeFilters.backend_stack}' = ANY(backend_stack)`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `
SELECT
problem_solving,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY problem_solving
ORDER BY total DESC
LIMIT 100;`
}
export function ProblemBeingSolvedChart() {
return (
<GenericChartWithQuery
title="What problem is your startup solving?"
targetColumn="problem_solving"
filterColumns={['currently_monetizing', 'money_raised', 'backend_stack']}
generateSQLQuery={generateProblemBeingSolvedSQL}
/>
)
}
@@ -1,349 +0,0 @@
'use client'
import { useEffect, useState } from 'react'
import { createClient } from '@supabase/supabase-js'
import {
Bar,
BarChart,
PieChart,
Pie,
CartesianGrid,
LabelList,
XAxis,
YAxis,
Cell,
} from 'recharts'
import { Card, CardContent, CardHeader, CardTitle } from 'ui'
import CodeBlock from '~/components/CodeBlock/CodeBlock'
import type { LANG } from '~/components/CodeBlock/CodeBlock'
import { ChartConfig, ChartContainer } from 'ui'
import { SurveyCodeWindow } from './SurveyCodeWindow'
import { FilterDropdown } from './FilterDropdown'
const chartConfig = {
value: {
label: 'Value',
color: 'hsl(var(--brand-default))',
},
label: {
color: '#ff0000',
},
} satisfies ChartConfig
// Create a separate Supabase client for your external project
const externalSupabase = createClient(
process.env.NEXT_PUBLIC_SURVEY_SUPABASE_URL,
process.env.NEXT_PUBLIC_SURVEY_SUPABASE_ANON_KEY
)
// Custom hook to fetch filter options from Supabase
function useFilterOptions(filterColumns: string[]) {
const [filters, setFilters] = useState({})
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
async function fetchFilterOptions() {
try {
setIsLoading(true)
setError(null)
console.log('Fetching filter options from Supabase for columns:', filterColumns)
const filterOptions = {}
for (const column of filterColumns) {
const { data, error: fetchError } = await externalSupabase
.from('responses_2025')
.select(column)
.not(column, 'is', null)
// Remove this line for boolean columns - it excludes false values
// .not(column, 'eq', '')
if (fetchError) {
console.error(`Error fetching ${column} options:`, fetchError)
continue
}
console.log(`Raw data for ${column}:`, data)
// Extract all individual values from PostgreSQL arrays
const allValues = data.flatMap((row) => {
const value = row[column]
console.log(
`Processing value for ${column}:`,
value,
typeof value,
Array.isArray(value)
)
if (Array.isArray(value)) {
// Handle PostgreSQL arrays
return value.filter((item) => item && item.length > 0)
} else if (typeof value === 'string' && value.startsWith('{') && value.endsWith('}')) {
// Handle PostgreSQL array format like {Firebase,MongoDB,MySQL}
const innerContent = value.slice(1, -1) // Remove { and }
if (innerContent.trim() === '') {
return []
}
return innerContent
.split(',')
.map((item) => item.trim())
.filter((item) => item.length > 0)
}
return [value]
})
console.log(`All values for ${column}:`, allValues)
// Get unique values and sort them
const uniqueValues = [...new Set(allValues)].sort()
console.log(`Unique values for ${column}:`, uniqueValues)
filterOptions[column] = {
label: column.replace('_', ' ').replace(/\b\w/g, (l) => l.toUpperCase()),
options: [
{
value: 'unset',
label: `All ${column.replace('_', ' ').replace(/\b\w/g, (l) => l.toUpperCase())}s`,
},
...uniqueValues.map((value) => ({
value: String(value), // Convert to string for React
label: String(value), // Convert to string for display
})),
],
}
}
console.log('Final filter options:', filterOptions)
setFilters(filterOptions)
} catch (err) {
console.error('Error fetching filter options:', err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
fetchFilterOptions()
}, [filterColumns])
return { filters, isLoading, error }
}
// Custom hook to fetch survey data using SQL query via RPC
// TODO: switch out to use supabase-js instead
function useSurveyData(sqlQuery: string) {
const [chartData, setChartData] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
async function fetchData() {
try {
setIsLoading(true)
setError(null)
console.log('Executing SQL query:', sqlQuery)
// Execute the SQL query using Supabase RPC
const { data, error: fetchError } = await externalSupabase.rpc('execute_sql', {
query: sqlQuery,
})
if (fetchError) {
console.error('Error executing SQL query:', fetchError)
setError(fetchError.message)
return
}
console.log('Raw data from SQL query:', data)
// Calculate total for percentage calculation
const total = data.reduce((sum, row) => sum + parseInt(row.total), 0)
// Transform the data to match chart format
const processedData = data.map((row) => ({
label: row.label || row.value || row[Object.keys(row)[0]], // Get the first column as label
value: total > 0 ? parseFloat(((parseInt(row.total) / total) * 100).toFixed(1)) : 0,
}))
console.log('Processed chart data:', processedData)
setChartData(processedData)
} catch (err) {
console.error('Error in fetchData:', err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
fetchData()
}, [sqlQuery])
return { chartData, isLoading, error }
}
export function RechartsChartWithQuery({
title,
targetColumn,
filterColumns,
generateSQLQuery,
chartType = 'bar', // TODO: Could be 'bar', 'pie', etc.
}) {
// Get dynamic filter options
const {
filters,
isLoading: filtersLoading,
error: filtersError,
} = useFilterOptions(filterColumns)
// Start with all filters unset (showing "all")
const [activeFilters, setActiveFilters] = useState(
filterColumns.reduce((acc, col) => ({ ...acc, [col]: 'unset' }), {})
)
// Generate the SQL query string
const sqlQuery = generateSQLQuery(activeFilters)
// Use the custom hook to fetch data
const { chartData, isLoading: dataLoading, error: dataError } = useSurveyData(sqlQuery)
const setFilterValue = (filterKey, value) => {
setActiveFilters((prev) => ({
...prev,
[filterKey]: value,
}))
}
const isLoading = filtersLoading || dataLoading
const error = filtersError || dataError
// Two different color arrays
// One for gradation (e.g. "Yes", "Maybe", "No")
const COLORS = [
'hsl(var(--brand-default))',
'hsl(var(--brand-500))',
'hsl(var(--foreground-light))',
]
// One for a clear binary answer (e.g. "Yes", "No")
const COLORS_BINARY = ['hsl(var(--brand-default))', 'hsl(var(--foreground-muted))']
return (
<Card className="w-full overflow-hidden">
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>
{isLoading ? (
<div className="flex items-center justify-center h-64">
<div className="text-foreground-lighter">Loading data...</div>
</div>
) : error ? (
<div className="flex items-center justify-center h-64">
<div className="text-red-500">Error: {error}</div>
</div>
) : (
<ChartContainer config={chartConfig}>
{chartType === 'bar' && (
<BarChart
accessibilityLayer
data={chartData}
layout="vertical"
margin={{
right: 0,
}}
>
<CartesianGrid horizontal={false} />
<YAxis
dataKey="label"
type="category"
tickLine={false}
tickMargin={64}
axisLine={false}
hide={false}
width={64}
tick={{
className: 'text-foreground-lighter',
fontSize: 12,
textAnchor: 'start',
dx: 0,
}}
/>
<XAxis dataKey="value" type="number" hide />
<Bar dataKey="value" layout="vertical" radius={4}>
{chartData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={index === 0 ? COLORS_BINARY[0] : COLORS_BINARY[1]}
/>
))}
<LabelList
dataKey="value"
position="right"
offset={8}
className="fill-foreground"
fontSize={12}
formatter={(value: number) => `${value}%`}
/>
</Bar>
</BarChart>
)}
{chartType === 'pie' && (
<PieChart>
<Pie
data={chartData}
dataKey="value"
innerRadius={60}
outerRadius={100}
// startAngle={15}
paddingAngle={2}
// label={renderCustomizedLabel}
>
{chartData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={
chartData.length === 2
? COLORS_BINARY[index % COLORS_BINARY.length]
: COLORS[index % COLORS.length]
}
/>
))}
</Pie>
</PieChart>
)}
</ChartContainer>
)}
</CardContent>
{/* <SurveyCodeWindow
code={sqlQuery}
lang="sql"
className="w-full"
filters={filters}
activeFilters={activeFilters}
setFilterValue={setFilterValue}
/> */}
<div className="flex flex-col">
{filters && activeFilters && setFilterValue && (
<div className="flex flex-wrap gap-4 p-4 border-b border-border">
{Object.entries(filters).map(([filterKey, filterConfig]) => (
<FilterDropdown
filterKey={filterKey}
filterConfig={filterConfig}
selectedValue={activeFilters[filterKey]}
setFilterValue={setFilterValue}
/>
))}
</div>
)}
{/* <CodeBlock lang="sql" size="small" showLineNumbers={true} rounded={false}>
{sqlQuery}
</CodeBlock> */}
</div>
</Card>
)
}
@@ -1,7 +1,7 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
// Generate SQL query for team count chart
function generateRoleSQL(activeFilters) {
function generateRoleSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.person_age !== 'unset') {
@@ -1,45 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateSubscriptionsSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.headquarters !== 'unset') {
whereClauses.push(`headquarters = '${activeFilters.headquarters}'`)
}
if (activeFilters.industry_normalized !== 'unset') {
whereClauses.push(`industry_normalized = '${activeFilters.industry_normalized}'`)
}
if (activeFilters.currently_monetizing !== 'unset') {
whereClauses.push(`currently_monetizing = '${activeFilters.currently_monetizing}'`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `
SELECT
unnest(subscriptions) AS technology,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY technology
UNION ALL
SELECT
'None of the above' AS technology,
COUNT(*) AS total
FROM responses_2025
WHERE subscriptions = '{}'
ORDER BY total DESC;
`
}
export function SubscriptionsChart() {
return (
<GenericChartWithQuery
title="Which of the following subscriptions does your startup pay for your team?"
targetColumn="subscriptions"
filterColumns={['headquarters', 'industry_normalized', 'currently_monetizing']}
generateSQLQuery={generateSubscriptionsSQL}
/>
)
}
@@ -1,53 +1,23 @@
// import SectionContainer from '~/components/Layouts/SectionContainer'
import { SurveyStatCard } from './SurveyStatCard'
import { SurveyPullQuote } from './SurveyPullQuote'
import { SurveyWordCloud } from './SurveyWordCloud'
import { SurveySummarizedAnswer } from './SurveySummarizedAnswer'
import { SurveyRankedAnswersPair } from './SurveyRankedAnswersPair'
// Charts (TODO: delete unused)
import { SurveySectionBreak } from './SurveySectionBreak'
import { AcceleratorParticipationChart } from './AcceleratorParticipationChart'
import { RoleChart } from './RoleChart'
import { IndustryChart } from './IndustryChart'
import { FundingStageChart } from './FundingStageChart'
import { DatabasesChart } from './DatabasesChart'
import { AIModelsChart } from './AIModelsChart'
// import { TeamCountChart } from '~/components/SurveyResults/TeamCountChart'
// import { TechnicalFoundersChart } from '~/components/SurveyResults/TechnicalFoundersChart'
// import { PreviousCompanyChart } from '~/components/SurveyResults/PreviousCompanyChart'
// import { FundingStageChart } from '~/components/SurveyResults/FundingStageChart'
import { HeadquartersChart } from '~/components/SurveyResults/HeadquartersChart'
// import { SurveyPullQuote } from '~/components/SurveyResults/SurveyPullQuote'
// import { SurveyStatCard } from '~/components/SurveyResults/SurveyStatCard'
// import { RoleChart } from '~/components/SurveyResults/RoleChart'
// import { ProblemBeingSolvedChart } from '~/components/SurveyResults/ProblemBeingSolvedChart'
// import { PersonAgeChart } from '~/components/SurveyResults/AgeChart'
// import { MonetizationChart } from '~/components/SurveyResults/MonetizationChart'
// import { AcceleratorParticipationChart } from '~/components/SurveyResults/AcceleratorParticipationChart'
// import { PivotFreqChart } from '~/components/SurveyResults/PivotFreqChart'
// import { FrontendStackChart } from '~/components/SurveyResults/FrontendStackChart'
// import { BackendStackChart } from '~/components/SurveyResults/BackendStackChart'
import { SalesToolsChart } from '~/components/SurveyResults/SalesToolsChart'
// import { PricingChart } from '~/components/SurveyResults/PricingChart'
// import { IndustryChart } from '~/components/SurveyResults/IndustryChart'
// import { DataWarehousesChart } from '~/components/SurveyResults/DataWarehousesChart'
// import { VectorDatabasesChart } from '~/components/SurveyResults/VectorDatabasesChart'
// import { OpenTelemetryChart } from '~/components/SurveyResults/OpenTelemetryChart'
// import { ObservabilityChart } from '~/components/SurveyResults/ObservabilityChart'
// import { CloudProvidersChart } from '~/components/SurveyResults/CloudProvidersChart'
// import { MustHaveDevToolsChart } from '~/components/SurveyResults/MustHaveDevToolsChart'
import { AICodingToolsChart } from './AICodingToolsChart'
// import { SubscriptionsChart } from '~/components/SurveyResults/SubscriptionsChart'
// import { NewslettersPaidForChart } from '~/components/SurveyResults/NewslettersPaidForChart'
// import { PodcastsListenedToChart } from '~/components/SurveyResults/PodcastsListenedToChart'
import { RegularSocialMediaUseChart } from '~/components/SurveyResults/RegularSocialMediaUseChart'
import { NewIdeasChart } from '~/components/SurveyResults/NewIdeasChart'
import { InitialPayingCustomersChart } from '~/components/SurveyResults/InitialPayingCustomersChart'
import { WorldOutlookChart } from '~/components/SurveyResults/WorldOutlookChart'
import { BiggestChallengeChart } from '~/components/SurveyResults/BiggestChallengeChart'
import { SurveySectionBreak } from '~/components/SurveyResults/SurveySectionBreak'
interface SurveyChapterSectionProps {
number: string
title: string
@@ -1,45 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
// Generate SQL query for team count chart
function generateTeamCountSQL(activeFilters) {
const whereClauses = []
if (activeFilters.headquarters !== 'unset') {
whereClauses.push(`headquarters = '${activeFilters.headquarters}'`)
}
if (activeFilters.funding_stage !== 'unset') {
whereClauses.push(`funding_stage = '${activeFilters.funding_stage}'`)
}
if (activeFilters.person_age !== 'unset') {
whereClauses.push(`person_age = '${activeFilters.person_age}'`)
}
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `SELECT
team_count,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY team_count
ORDER BY
CASE team_count
WHEN '1-10' THEN 1
WHEN '11-50' THEN 2
WHEN '51-100' THEN 3
WHEN '101-250' THEN 4
WHEN '250' THEN 5
END;`
}
export function TeamCountChart() {
return (
<GenericChartWithQuery
title="How many full-time employees does your startup have?"
targetColumn="team_count"
filterColumns={['headquarters', 'funding_stage', 'person_age']}
generateSQLQuery={generateTeamCountSQL}
/>
)
}
@@ -1,42 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateTechnicalFoundersSQL(activeFilters: Record<string, string>) {
const whereClauses = []
// Always filter out NULL values
whereClauses.push(`founders_are_technical IS NOT NULL`)
if (activeFilters.person_age !== 'unset') {
whereClauses.push(`person_age = '${activeFilters.person_age}'`)
}
if (activeFilters.money_raised !== 'unset') {
whereClauses.push(`money_raised = '${activeFilters.money_raised}'`)
}
const whereClause = `WHERE ${whereClauses.join('\n AND ')}`
return `SELECT
CASE
WHEN founders_are_technical = true THEN 'Yes'
WHEN founders_are_technical = false THEN 'No'
-- ELSE 'Unknown'
END AS founders_are_technical,
COUNT(*) AS total
FROM responses_2025
${whereClause}
GROUP BY founders_are_technical
ORDER BY total DESC;`
}
export function TechnicalFoundersChart() {
return (
<GenericChartWithQuery
title="Are the founder(s) at your current startup technical?"
targetColumn="founders_are_technical"
filterColumns={['person_age', 'money_raised']}
generateSQLQuery={generateTechnicalFoundersSQL}
chartType="pie"
/>
)
}
@@ -1,32 +0,0 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
function generateVectorDatabasesSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.databases !== 'unset') {
whereClauses.push(`'${activeFilters.databases}' = ANY(databases)`)
}
// TODO: Returns actual row values (e.g. "Weweb,Svelte,React") instead of just "Svelte"
// FilterDropdown should be able to handle these as separate, unique values
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join('\n AND ')}` : ''
return `SELECT
unnest(vector_databases) AS technology,
COUNT(*) AS total
FROM responses_2025${whereClause ? '\n' + whereClause : ''}
GROUP BY technology
ORDER BY total DESC;
`
}
export function VectorDatabasesChart() {
return (
<GenericChartWithQuery
title="Which vector database(s) is your startup using? (Filtered)"
targetColumn="vector_databases"
filterColumns={['databases']}
generateSQLQuery={generateVectorDatabasesSQL}
/>
)
}
@@ -1,7 +1,7 @@
import { GenericChartWithQuery } from './GenericChartWithQuery'
// Generate SQL query for team count chart
function generateWorldOutlookSQL(activeFilters) {
function generateWorldOutlookSQL(activeFilters: Record<string, string>) {
const whereClauses = []
if (activeFilters.role !== 'unset') {