Skip to content

Commit

Permalink
implement csv upload functionality and refactor index page
Browse files Browse the repository at this point in the history
  • Loading branch information
ligsnf committed Dec 17, 2024
1 parent de45151 commit 71a18a5
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 101 deletions.
36 changes: 36 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"next-themes": "^0.4.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.3.5",
"react-hook-form": "^7.54.0",
"recharts": "^2.14.1",
"sonner": "^1.7.0",
Expand Down
30 changes: 30 additions & 0 deletions src/components/csv/csv-information-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Info } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"

export function CSVInformationDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size="icon" className="min-w-10">
<Info className="text-primary" strokeWidth={2.5} />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Instructions for preparing CSV</DialogTitle>
<DialogDescription>
yeah mate
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
)
}
11 changes: 11 additions & 0 deletions src/components/csv/csv-upload-alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { cn } from "@/lib/utils"
import { TriangleAlert } from "lucide-react"

export function CSVUploadAlert({ className }: { className?: string }) {
return (
<div className={cn("flex items-center gap-2 p-2 border rounded-md text-sm border-[#fdf5d3] dark:border-[#3d3d00] text-[#dc7609] dark:text-[#f3cf58] [&>svg]:text-[#dc7609] [&>svg]:dark:text-[#f3cf58]", className)}>
<TriangleAlert className="h-4 w-4 !top-auto" />
Uploading CSV will replace all current data.
</div>
)
}
41 changes: 41 additions & 0 deletions src/components/csv/csv-upload-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Upload } from "lucide-react"
import { CSVUploadAlert } from "@/components/csv/csv-upload-alert"
import { CSVUploader } from "@/components/csv/csv-uploader"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"

export function CSVUploadDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">
<Upload className="" strokeWidth={2.5} />
<span className="sm:hidden">CSV</span>
<span className="hidden sm:inline">Upload CSV</span>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Upload CSV</DialogTitle>
</DialogHeader>
<CSVUploadAlert />
<DialogDescription className="hidden">
Upload a CSV file to replace all current data
</DialogDescription>
<CSVUploader
onCSVUpload={(csvData) => {
// Here you can process the CSV data
console.log(csvData)
}}
/>
</DialogContent>
</Dialog>
)
}
114 changes: 114 additions & 0 deletions src/components/csv/csv-uploader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import * as React from "react"
import { FileText, Upload, X } from "lucide-react"
import Dropzone, { DropzoneRootProps, DropzoneInputProps } from "react-dropzone"
import { toast } from "sonner"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"

interface CSVUploaderProps extends React.HTMLAttributes<HTMLDivElement> {
onCSVUpload: (data: string) => void
disabled?: boolean
className?: string
}

export function CSVUploader({ onCSVUpload, disabled = false, className }: CSVUploaderProps) {
const [file, setFile] = React.useState<File | null>(null)

const onDrop = React.useCallback(
(acceptedFiles: File[]) => {
if (acceptedFiles.length === 0) {
toast.error("Please upload a CSV file")
return
}

const csvFile = acceptedFiles[0]
setFile(csvFile)

// Read and process the CSV file
const reader = new FileReader()
reader.onload = (event) => {
if (event.target?.result) {
const csvData = event.target.result as string
onCSVUpload(csvData)
}
}
reader.readAsText(csvFile)
},
[onCSVUpload]
)

function onRemove() {
setFile(null)
}

return (
<div className="relative flex flex-col gap-4">
<Dropzone
onDrop={onDrop}
accept={{ "text/csv": [".csv"] }}
maxFiles={1}
multiple={false}
disabled={disabled}
>
{({ getRootProps, getInputProps, isDragActive }: {
getRootProps: () => DropzoneRootProps;
getInputProps: () => DropzoneInputProps;
isDragActive: boolean;
}) => (
<div
{...getRootProps()}
className={cn(
"group relative grid h-40 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed border-muted-foreground/25 px-5 py-2.5 text-center transition hover:bg-muted/25",
isDragActive && "border-muted-foreground/50",
disabled && "pointer-events-none opacity-60",
className
)}
>
<input {...getInputProps()} />
<div className="flex flex-col items-center justify-center gap-4">
<Upload
className="h-8 w-8 text-muted-foreground"
aria-hidden="true"
/>
<div className="flex flex-col gap-1">
<p className="hidden sm:inline font-medium text-muted-foreground">
{isDragActive
? "Drop the CSV file here"
: "Drag and drop a CSV file, or click to select"}
</p>
<p className="sm:hidden font-medium text-muted-foreground">
Click to select a CSV file
</p>
{/* <p className="text-sm text-muted-foreground/70">
You can only upload one file
</p> */}
</div>
</div>
</div>
)}
</Dropzone>

{file && (
<div className="flex items-center gap-2 rounded-lg border p-2">
<FileText className="h-6 w-6 text-muted-foreground" />
<div className="flex flex-1 flex-col">
<p className="text-sm font-medium">{file.name}</p>
<p className="text-xs text-muted-foreground">
{(file.size / 1024).toFixed(2)} KB
</p>
</div>
<Button
type="button"
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={onRemove}
>
<X className="h-4 w-4" />
<span className="sr-only">Remove file</span>
</Button>
</div>
)}
</div>
)
}
File renamed without changes.
53 changes: 53 additions & 0 deletions src/components/stat-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useBreakpoint } from '@/hooks/use-breakpoint'
import { calculateColor } from "@/lib/calculate"
import { RadialChart } from "@/components/radial-chart"
import { useTheme } from "@/components/theme/theme-provider"

import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card"

type StatCardProps = {
title: string
subtitle: string
value: number
maxValue: number
}

export function StatCard({ title, subtitle, value, maxValue }: StatCardProps) {
const { theme } = useTheme()
const isDarkMode = theme === "dark"
const color = calculateColor(value, maxValue, isDarkMode)
const { isMobile } = useBreakpoint()

return (
<Card>
<CardHeader>
<CardTitle className="md:text-center">
{title} <span className="hidden md:inline text-xl text-muted-foreground">({subtitle})</span>
</CardTitle>
</CardHeader>
<CardContent>
{!isMobile && (
<RadialChart
className="font-mono"
value={value}
maxValue={maxValue}
color={color}
/>
)}
{isMobile && (
<p
className="text-3xl sm:text-4xl font-bold font-mono"
style={{ color: color }}
>
{value}
</p>
)}
</CardContent>
</Card>
)
}
Loading

0 comments on commit 71a18a5

Please sign in to comment.