oh lord. prettier eslint and all that
This commit is contained in:
16
.vscode/settings.json
vendored
16
.vscode/settings.json
vendored
@@ -2,11 +2,7 @@
|
||||
"editor.codeActionsOnSave": { "source.fixAll.eslint": true },
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"[handlebars]": {
|
||||
"editor.formatOnSave": false,
|
||||
"editor.codeActionsOnSave": { "source.fixAll.eslint": false }
|
||||
},
|
||||
"files.associations": { "*.hbs": "handlebars" },
|
||||
"eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }],
|
||||
"eslint.workingDirectories": [
|
||||
{ "pattern": "apps/*/" },
|
||||
{ "pattern": "packages/*/" },
|
||||
@@ -14,10 +10,8 @@
|
||||
],
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"[yaml]": {
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2,
|
||||
"editor.autoIndent": "advanced"
|
||||
},
|
||||
"editor.inlineSuggest.enabled": true
|
||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||
"next/router.d.ts",
|
||||
"next/dist/client/router.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
const config = {
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: true,
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
extends: [
|
||||
"next/core-web-vitals",
|
||||
"plugin:@typescript-eslint/recommended-type-checked",
|
||||
"plugin:@typescript-eslint/stylistic-type-checked",
|
||||
],
|
||||
rules: {
|
||||
// These opinionated rules are enabled in stylistic-type-checked above.
|
||||
// Feel free to reconfigure them to your own preference.
|
||||
"@typescript-eslint/array-type": "off",
|
||||
"@typescript-eslint/consistent-type-definitions": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/no-unsafe-argument": "off",
|
||||
"@typescript-eslint/no-unsafe-return": "off",
|
||||
"@typescript-eslint/no-unsafe-call": "off",
|
||||
"@typescript-eslint/no-misused-promises": "off",
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"warn",
|
||||
{
|
||||
prefer: "type-imports",
|
||||
fixStyle: "inline-type-imports",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -24,17 +24,25 @@ RUN apt update \
|
||||
&& rm n \
|
||||
&& npm install -g n
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package.json
|
||||
COPY pnpm-lock.yaml pnpm-lock.yaml
|
||||
COPY pnpm-workspace.yaml pnpm-workspace.yaml
|
||||
COPY apps/web/package.json apps/web/package.json
|
||||
COPY packages/types/package.json packages/types/package.json
|
||||
|
||||
FROM base AS build
|
||||
COPY . /app
|
||||
WORKDIR /app/apps/web
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts
|
||||
COPY . /app
|
||||
RUN pnpm dlx prisma generate
|
||||
RUN pnpm run build
|
||||
|
||||
FROM base AS prod
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
WORKDIR /app/apps/web
|
||||
RUN pnpm install --frozen-lockfile --prod --ignore-scripts
|
||||
RUN pnpm dlx prisma generate
|
||||
|
||||
FROM base AS runner
|
||||
COPY --from=build /app/package.json /app/package.json
|
||||
@@ -42,6 +50,5 @@ COPY --from=prod /app/node_modules /app/node_modules
|
||||
COPY --from=build /app/apps/web /app/apps/web
|
||||
COPY --from=prod /app/apps/web/node_modules /app/apps/web/node_modules
|
||||
WORKDIR /app/apps/web
|
||||
RUN pnpm dlx prisma generate
|
||||
EXPOSE 3000
|
||||
CMD [ "pnpm", "start" ]
|
||||
@@ -7,7 +7,9 @@ await import('./src/env.mjs');
|
||||
/** @type {import("next").NextConfig} */
|
||||
const config = {
|
||||
reactStrictMode: true,
|
||||
|
||||
transpilePackages: [],
|
||||
eslint: { ignoreDuringBuilds: true },
|
||||
typescript: { ignoreBuildErrors: true },
|
||||
/**
|
||||
* If you are using `appDir` then you must comment the below `i18n` config out.
|
||||
*
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --check \"**/*.{mjs,ts,md,json}\"",
|
||||
"format": "prettier --write \"**/*.{tsx,mjs,ts,md,json}\"",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -62,20 +62,23 @@
|
||||
"devDependencies": {
|
||||
"@mixan/eslint-config": "workspace:*",
|
||||
"@mixan/prettier-config": "workspace:*",
|
||||
"@mixan/tsconfig": "workspace:*",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/node": "^18.16.0",
|
||||
"@types/ramda": "^0.29.6",
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-syntax-highlighter": "^15.5.9",
|
||||
"@typescript-eslint/eslint-plugin": "^6.6.0",
|
||||
"@typescript-eslint/parser": "^6.6.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-next": "^13.5.4",
|
||||
"postcss": "^8.4.27",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-plugin-tailwindcss": "^0.5.1",
|
||||
"prisma": "^5.1.1",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.2.0"
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
"initVersion": "7.21.0"
|
||||
@@ -84,6 +87,7 @@
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@mixan/eslint-config/base",
|
||||
"@mixan/eslint-config/nextjs",
|
||||
"@mixan/eslint-config/react"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
/** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').options} */
|
||||
const config = {
|
||||
plugins: ['prettier-plugin-tailwindcss'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,3 +1,3 @@
|
||||
import AutoSizer from "react-virtualized-auto-sizer";
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
export { AutoSizer }
|
||||
export { AutoSizer };
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { type HtmlProps } from "@/types";
|
||||
import { cn } from "@/utils/cn";
|
||||
import type { HtmlProps } from '@/types';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
export function ButtonContainer({className,...props}: HtmlProps<HTMLDivElement>) {
|
||||
export function ButtonContainer({
|
||||
className,
|
||||
...props
|
||||
}: HtmlProps<HTMLDivElement>) {
|
||||
return (
|
||||
<div className={cn("flex justify-between mt-6", className)} {...props} />
|
||||
<div className={cn('flex justify-between mt-6', className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { type HtmlProps } from "@/types";
|
||||
import type { HtmlProps } from '@/types';
|
||||
|
||||
export function Card({children}: HtmlProps<HTMLDivElement>) {
|
||||
return (
|
||||
<div className="border border-border rounded">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export function Card({ children }: HtmlProps<HTMLDivElement>) {
|
||||
return <div className="border border-border rounded">{children}</div>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type HtmlProps } from "@/types";
|
||||
import { cn } from "@/utils/cn";
|
||||
import type { HtmlProps } from '@/types';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
type ColorSquareProps = HtmlProps<HTMLDivElement>;
|
||||
|
||||
@@ -7,8 +7,8 @@ export function ColorSquare({ children, className }: ColorSquareProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-5 w-5 flex-shrink-0 items-center justify-center rounded bg-purple-500 text-xs font-medium text-white [.mini_&]:h-4 [.mini_&]:w-4 [.mini_&]:text-[0.6rem]",
|
||||
className,
|
||||
'flex h-5 w-5 flex-shrink-0 items-center justify-center rounded bg-purple-500 text-xs font-medium text-white [.mini_&]:h-4 [.mini_&]:w-4 [.mini_&]:text-[0.6rem]',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { type HtmlProps } from "@/types";
|
||||
import { cn } from "@/utils/cn";
|
||||
import type { HtmlProps } from '@/types';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
export function Container({className,...props}: HtmlProps<HTMLDivElement>) {
|
||||
export function Container({ className, ...props }: HtmlProps<HTMLDivElement>) {
|
||||
return (
|
||||
<div className={cn("mx-auto w-full max-w-4xl px-4", className)} {...props} />
|
||||
<div
|
||||
className={cn('mx-auto w-full max-w-4xl px-4', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { cn } from "@/utils/cn";
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
type ContentHeaderProps = {
|
||||
interface ContentHeaderProps {
|
||||
title: string;
|
||||
text: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
}
|
||||
|
||||
export function ContentHeader({ title, text, children }: ContentHeaderProps) {
|
||||
return (
|
||||
@@ -18,12 +18,12 @@ export function ContentHeader({ title, text, children }: ContentHeaderProps) {
|
||||
);
|
||||
}
|
||||
|
||||
type ContentSectionProps = {
|
||||
interface ContentSectionProps {
|
||||
title: string;
|
||||
text?: string | React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
asCol?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export function ContentSection({
|
||||
title,
|
||||
@@ -34,8 +34,8 @@ export function ContentSection({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"first:pt-0] flex py-6",
|
||||
asCol ? "col flex" : "justify-between",
|
||||
'first:pt-0] flex py-6',
|
||||
asCol ? 'col flex' : 'justify-between'
|
||||
)}
|
||||
>
|
||||
{title && (
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {
|
||||
type ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
} from '@tanstack/react-table';
|
||||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -11,17 +12,14 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "./ui/table";
|
||||
} from './ui/table';
|
||||
|
||||
interface DataTableProps<TData> {
|
||||
columns: ColumnDef<TData, any>[];
|
||||
data: TData[];
|
||||
}
|
||||
|
||||
export function DataTable<TData>({
|
||||
columns,
|
||||
data,
|
||||
}: DataTableProps<TData>) {
|
||||
export function DataTable<TData>({ columns, data }: DataTableProps<TData>) {
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
@@ -40,7 +38,7 @@ export function DataTable<TData>({
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
@@ -53,7 +51,7 @@ export function DataTable<TData>({
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { cloneElement } from "react";
|
||||
import { cloneElement } from 'react';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -7,19 +8,24 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "./ui/dropdown-menu";
|
||||
} from './ui/dropdown-menu';
|
||||
|
||||
type DropdownProps<Value> = {
|
||||
interface DropdownProps<Value> {
|
||||
children: React.ReactNode;
|
||||
label?: string;
|
||||
items: Array<{
|
||||
items: {
|
||||
label: string;
|
||||
value: Value;
|
||||
}>;
|
||||
}[];
|
||||
onChange?: (value: Value) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export function Dropdown<Value extends string>({ children, label, items, onChange }: DropdownProps<Value>) {
|
||||
export function Dropdown<Value extends string>({
|
||||
children,
|
||||
label,
|
||||
items,
|
||||
onChange,
|
||||
}: DropdownProps<Value>) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type HtmlProps } from "@/types";
|
||||
import type { HtmlProps } from '@/types';
|
||||
|
||||
type PageTitleProps = HtmlProps<HTMLDivElement>;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { Button } from './ui/button';
|
||||
|
||||
export function usePagination(take = 100) {
|
||||
const [skip, setSkip] = useState(0);
|
||||
@@ -12,11 +13,11 @@ export function usePagination(take = 100) {
|
||||
canPrev: skip > 0,
|
||||
canNext: true,
|
||||
}),
|
||||
[skip, setSkip, take],
|
||||
[skip, setSkip, take]
|
||||
);
|
||||
}
|
||||
|
||||
export type PaginationProps = ReturnType<typeof usePagination>
|
||||
export type PaginationProps = ReturnType<typeof usePagination>;
|
||||
|
||||
export function Pagination(props: PaginationProps) {
|
||||
return (
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import ts from "react-syntax-highlighter/dist/cjs/languages/hljs/typescript";
|
||||
import docco from "react-syntax-highlighter/dist/cjs/styles/hljs/docco";
|
||||
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript';
|
||||
import docco from 'react-syntax-highlighter/dist/cjs/styles/hljs/docco';
|
||||
|
||||
SyntaxHighlighter.registerLanguage("typescript", ts);
|
||||
SyntaxHighlighter.registerLanguage('typescript', ts);
|
||||
|
||||
type SyntaxProps = {
|
||||
interface SyntaxProps {
|
||||
code: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function Syntax({ code }: SyntaxProps) {
|
||||
return <SyntaxHighlighter style={docco}>{code}</SyntaxHighlighter>;
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { type HtmlProps } from "@/types";
|
||||
import type { HtmlProps } from '@/types';
|
||||
|
||||
type WithSidebarProps = HtmlProps<HTMLDivElement>
|
||||
type WithSidebarProps = HtmlProps<HTMLDivElement>;
|
||||
|
||||
export function WithSidebar({children}: WithSidebarProps) {
|
||||
export function WithSidebar({ children }: WithSidebarProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-[200px_minmax(0,1fr)] gap-8">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
<div className="grid grid-cols-[200px_minmax(0,1fr)] gap-8">{children}</div>
|
||||
);
|
||||
}
|
||||
|
||||
type SidebarProps = HtmlProps<HTMLDivElement>
|
||||
type SidebarProps = HtmlProps<HTMLDivElement>;
|
||||
|
||||
export function Sidebar({children}: SidebarProps) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export function Sidebar({ children }: SidebarProps) {
|
||||
return <div className="flex flex-col gap-1">{children}</div>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { useRefetchActive } from '@/hooks/useRefetchActive';
|
||||
import { pushModal, showConfirm } from '@/modals';
|
||||
import type { IClientWithProject } from '@/types';
|
||||
import { api } from '@/utils/api';
|
||||
import { clipboard } from '@/utils/clipboard';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -5,27 +13,20 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
import { Button } from "../ui/button";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
import { pushModal, showConfirm } from "@/modals";
|
||||
import { type IClientWithProject } from "@/types";
|
||||
import { clipboard } from "@/utils/clipboard";
|
||||
import { api } from "@/utils/api";
|
||||
import { useRefetchActive } from "@/hooks/useRefetchActive";
|
||||
import { toast } from "../ui/use-toast";
|
||||
} from '../ui/dropdown-menu';
|
||||
import { toast } from '../ui/use-toast';
|
||||
|
||||
export function ClientActions({ id }: IClientWithProject) {
|
||||
const refetch = useRefetchActive()
|
||||
const refetch = useRefetchActive();
|
||||
const deletion = api.client.remove.useMutation({
|
||||
onSuccess() {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Client revoked, incoming requests will be rejected.',
|
||||
})
|
||||
refetch()
|
||||
}
|
||||
})
|
||||
});
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -41,7 +42,7 @@ export function ClientActions({ id }: IClientWithProject) {
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
pushModal("EditClient", { id });
|
||||
pushModal('EditClient', { id });
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
@@ -56,9 +57,9 @@ export function ClientActions({ id }: IClientWithProject) {
|
||||
onConfirm() {
|
||||
deletion.mutate({
|
||||
id,
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Revoke
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { formatDate } from "@/utils/date";
|
||||
import { type ColumnDef } from "@tanstack/react-table";
|
||||
import { type IClientWithProject } from "@/types";
|
||||
import { ClientActions } from "./ClientActions";
|
||||
import type { IClientWithProject } from '@/types';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
|
||||
import { ClientActions } from './ClientActions';
|
||||
|
||||
export const columns: ColumnDef<IClientWithProject>[] = [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Name",
|
||||
accessorKey: 'name',
|
||||
header: 'Name',
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div>
|
||||
@@ -19,25 +20,25 @@ export const columns: ColumnDef<IClientWithProject>[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "Client ID",
|
||||
accessorKey: 'id',
|
||||
header: 'Client ID',
|
||||
},
|
||||
{
|
||||
accessorKey: "secret",
|
||||
header: "Secret",
|
||||
accessorKey: 'secret',
|
||||
header: 'Secret',
|
||||
cell: () => <div className="italic text-muted-foreground">Hidden</div>,
|
||||
},
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: "Created at",
|
||||
accessorKey: 'createdAt',
|
||||
header: 'Created at',
|
||||
cell({ row }) {
|
||||
const date = row.original.createdAt;
|
||||
return <div>{formatDate(date)}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "Actions",
|
||||
id: 'actions',
|
||||
header: 'Actions',
|
||||
cell: ({ row }) => <ClientActions {...row.original} />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,66 +1,70 @@
|
||||
import { useMemo } from "react";
|
||||
import { DataTable } from "@/components/DataTable";
|
||||
import { type PaginationProps, Pagination } from "@/components/Pagination";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
||||
import { type RouterOutputs } from "@/utils/api";
|
||||
import { formatDateTime } from "@/utils/date";
|
||||
import { toDots } from "@/utils/object";
|
||||
import { AvatarImage } from "@radix-ui/react-avatar";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import Link from "next/link";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { useMemo } from 'react';
|
||||
import { DataTable } from '@/components/DataTable';
|
||||
import { Pagination } from '@/components/Pagination';
|
||||
import type { PaginationProps } from '@/components/Pagination';
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import { Table, TableBody, TableCell, TableRow } from '@/components/ui/table';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import type { RouterOutputs } from '@/utils/api';
|
||||
import { formatDateTime } from '@/utils/date';
|
||||
import { toDots } from '@/utils/object';
|
||||
import { AvatarImage } from '@radix-ui/react-avatar';
|
||||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import Link from 'next/link';
|
||||
|
||||
const columnHelper =
|
||||
createColumnHelper<RouterOutputs["event"]["list"][number]>();
|
||||
createColumnHelper<RouterOutputs['event']['list'][number]>();
|
||||
|
||||
type EventsTableProps = {
|
||||
data: RouterOutputs["event"]["list"];
|
||||
interface EventsTableProps {
|
||||
data: RouterOutputs['event']['list'];
|
||||
pagination: PaginationProps;
|
||||
};
|
||||
}
|
||||
|
||||
export function EventsTable({ data, pagination }: EventsTableProps) {
|
||||
const params = useOrganizationParams()
|
||||
const params = useOrganizationParams();
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
columnHelper.accessor((row) => row.createdAt, {
|
||||
id: "createdAt",
|
||||
header: () => "Created At",
|
||||
id: 'createdAt',
|
||||
header: () => 'Created At',
|
||||
cell(info) {
|
||||
return formatDateTime(info.getValue());
|
||||
},
|
||||
footer: () => "Created At",
|
||||
footer: () => 'Created At',
|
||||
}),
|
||||
columnHelper.accessor((row) => row.name, {
|
||||
id: "event",
|
||||
header: () => "Event",
|
||||
id: 'event',
|
||||
header: () => 'Event',
|
||||
cell(info) {
|
||||
return <span className="font-medium">{info.getValue()}</span>;
|
||||
},
|
||||
footer: () => "Created At",
|
||||
footer: () => 'Created At',
|
||||
}),
|
||||
columnHelper.accessor((row) => row.profile, {
|
||||
id: "profile",
|
||||
header: () => "Profile",
|
||||
id: 'profile',
|
||||
header: () => 'Profile',
|
||||
cell(info) {
|
||||
const profile = info.getValue();
|
||||
return (
|
||||
<Link href={`/${params.organization}/${params.project}/profiles/${profile?.id}`} className="flex items-center gap-2">
|
||||
<Link
|
||||
href={`/${params.organization}/${params.project}/profiles/${profile?.id}`}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Avatar className="h-6 w-6">
|
||||
{profile?.avatar && <AvatarImage src={profile.avatar} />}
|
||||
<AvatarFallback className="text-xs">
|
||||
{profile?.first_name?.at(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{`${profile?.first_name} ${profile?.last_name ?? ""}`}
|
||||
{`${profile?.first_name} ${profile?.last_name ?? ''}`}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
footer: () => "Created At",
|
||||
footer: () => 'Created At',
|
||||
}),
|
||||
columnHelper.accessor((row) => row.properties, {
|
||||
id: "properties",
|
||||
header: () => "Properties",
|
||||
id: 'properties',
|
||||
header: () => 'Properties',
|
||||
cell(info) {
|
||||
const dots = toDots(info.getValue() as Record<string, any>);
|
||||
return (
|
||||
@@ -71,10 +75,10 @@ export function EventsTable({ data, pagination }: EventsTableProps) {
|
||||
<TableRow key={key}>
|
||||
<TableCell className="font-medium">{key}</TableCell>
|
||||
<TableCell>
|
||||
{typeof dots[key] === "boolean"
|
||||
{typeof dots[key] === 'boolean'
|
||||
? dots[key]
|
||||
? "true"
|
||||
: "false"
|
||||
? 'true'
|
||||
: 'false'
|
||||
: dots[key]}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -84,7 +88,7 @@ export function EventsTable({ data, pagination }: EventsTableProps) {
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
footer: () => "Created At",
|
||||
footer: () => 'Created At',
|
||||
}),
|
||||
];
|
||||
}, []);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
type InputErrorProps = { message?: string };
|
||||
interface InputErrorProps {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export function InputError({ message }: InputErrorProps) {
|
||||
if (!message) {
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { forwardRef } from "react";
|
||||
import { Input, type InputProps } from "../ui/input";
|
||||
import { Label } from "../ui/label";
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { Input } from '../ui/input';
|
||||
import type { InputProps } from '../ui/input';
|
||||
import { Label } from '../ui/label';
|
||||
|
||||
type InputWithLabelProps = InputProps & {
|
||||
label: string;
|
||||
};
|
||||
|
||||
export const InputWithLabel = forwardRef<HTMLInputElement, InputWithLabelProps>(({ label, ...props }, ref) => {
|
||||
return (
|
||||
<div>
|
||||
<Label htmlFor={label} className="block mb-2">{label}</Label>
|
||||
<Input ref={ref} id={label} {...props} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export const InputWithLabel = forwardRef<HTMLInputElement, InputWithLabelProps>(
|
||||
({ label, ...props }, ref) => {
|
||||
return (
|
||||
<div>
|
||||
<Label htmlFor={label} className="block mb-2">
|
||||
{label}
|
||||
</Label>
|
||||
<Input ref={ref} id={label} {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
InputWithLabel.displayName = "InputWithLabel";
|
||||
InputWithLabel.displayName = 'InputWithLabel';
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { NavbarUserDropdown } from "../navbar/NavbarUserDropdown";
|
||||
import { NavbarMenu } from "../navbar/NavbarMenu";
|
||||
import { Container } from "../Container";
|
||||
import Link from "next/link";
|
||||
import Link from 'next/link';
|
||||
|
||||
type MainLayoutProps = {
|
||||
import { Container } from '../Container';
|
||||
import { NavbarMenu } from '../navbar/NavbarMenu';
|
||||
import { NavbarUserDropdown } from '../navbar/NavbarUserDropdown';
|
||||
|
||||
interface MainLayoutProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function MainLayout({ children, className }: MainLayoutProps) {
|
||||
return (
|
||||
@@ -14,7 +15,9 @@ export function MainLayout({ children, className }: MainLayoutProps) {
|
||||
<div className="h-2 w-full bg-gradient-to-r from-blue-900 to-purple-600"></div>
|
||||
<nav className="border-b border-border">
|
||||
<Container className="flex h-20 items-center justify-between ">
|
||||
<Link href="/" className="text-3xl">mixan</Link>
|
||||
<Link href="/" className="text-3xl">
|
||||
mixan
|
||||
</Link>
|
||||
<div className="flex items-center gap-8">
|
||||
<NavbarMenu />
|
||||
<div>
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
import { Container } from "../Container";
|
||||
import { MainLayout } from "./MainLayout";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Sidebar, WithSidebar } from "../WithSidebar";
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/utils/cn";
|
||||
import { PageTitle } from "../PageTitle";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { cn } from '@/utils/cn';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
type SettingsLayoutProps = {
|
||||
import { Container } from '../Container';
|
||||
import { PageTitle } from '../PageTitle';
|
||||
import { Sidebar, WithSidebar } from '../WithSidebar';
|
||||
import { MainLayout } from './MainLayout';
|
||||
|
||||
interface SettingsLayoutProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function SettingsLayout({ children, className }: SettingsLayoutProps) {
|
||||
const params = useOrganizationParams();
|
||||
const pathname = usePathname();
|
||||
const links = [
|
||||
{ href: `/${params.organization}/settings/organization`, label: "Organization" },
|
||||
{ href: `/${params.organization}/settings/projects`, label: "Projects" },
|
||||
{ href: `/${params.organization}/settings/clients`, label: "Clients" },
|
||||
{ href: `/${params.organization}/settings/profile`, label: "Profile" },
|
||||
{
|
||||
href: `/${params.organization}/settings/organization`,
|
||||
label: 'Organization',
|
||||
},
|
||||
{ href: `/${params.organization}/settings/projects`, label: 'Projects' },
|
||||
{ href: `/${params.organization}/settings/clients`, label: 'Clients' },
|
||||
{ href: `/${params.organization}/settings/profile`, label: 'Profile' },
|
||||
];
|
||||
return (
|
||||
<MainLayout>
|
||||
@@ -32,19 +36,17 @@ export function SettingsLayout({ children, className }: SettingsLayoutProps) {
|
||||
key={href}
|
||||
href={href}
|
||||
className={cn(
|
||||
"p-4 py-3 leading-none rounded-lg transition-colors",
|
||||
'p-4 py-3 leading-none rounded-lg transition-colors',
|
||||
pathname.startsWith(href)
|
||||
? "bg-slate-100"
|
||||
: "hover:bg-slate-100",
|
||||
? 'bg-slate-100'
|
||||
: 'hover:bg-slate-100'
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
))}
|
||||
</Sidebar>
|
||||
<div className={cn('flex flex-col', className)}>
|
||||
{children}
|
||||
</div>
|
||||
<div className={cn('flex flex-col', className)}>{children}</div>
|
||||
</WithSidebar>
|
||||
</Container>
|
||||
</MainLayout>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { LineChart } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -8,9 +7,10 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import Link from "next/link";
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { LineChart } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export function NavbarCreate() {
|
||||
const params = useOrganizationParams();
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import Link from "next/link";
|
||||
import { NavbarCreate } from "./NavbarCreate";
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { NavbarCreate } from './NavbarCreate';
|
||||
|
||||
export function NavbarMenu() {
|
||||
const params = useOrganizationParams()
|
||||
const params = useOrganizationParams();
|
||||
return (
|
||||
<div className="flex gap-6 items-center">
|
||||
<Link href={`/${params.organization}`}>Home</Link>
|
||||
{params.project && <Link href={`/${params.organization}/${params.project}/events`}>Events</Link>}
|
||||
{params.project && <Link href={`/${params.organization}/${params.project}/profiles`}>Profiles</Link>}
|
||||
{params.project && (
|
||||
<Link href={`/${params.organization}/${params.project}/events`}>
|
||||
Events
|
||||
</Link>
|
||||
)}
|
||||
{params.project && (
|
||||
<Link href={`/${params.organization}/${params.project}/profiles`}>
|
||||
Profiles
|
||||
</Link>
|
||||
)}
|
||||
<NavbarCreate />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -6,11 +6,11 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { User } from "lucide-react";
|
||||
import { signOut } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { User } from 'lucide-react';
|
||||
import { signOut } from 'next-auth/react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export function NavbarUserDropdown() {
|
||||
const params = useOrganizationParams();
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { useRefetchActive } from '@/hooks/useRefetchActive';
|
||||
import { pushModal, showConfirm } from '@/modals';
|
||||
import type { IProject } from '@/types';
|
||||
import { api } from '@/utils/api';
|
||||
import { clipboard } from '@/utils/clipboard';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -5,27 +13,20 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
import { Button } from "../ui/button";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
import { pushModal, showConfirm } from "@/modals";
|
||||
import { type IProject } from "@/types";
|
||||
import { clipboard } from "@/utils/clipboard";
|
||||
import { useRefetchActive } from "@/hooks/useRefetchActive";
|
||||
import { api } from "@/utils/api";
|
||||
import { toast } from "../ui/use-toast";
|
||||
} from '../ui/dropdown-menu';
|
||||
import { toast } from '../ui/use-toast';
|
||||
|
||||
export function ProjectActions({ id }: IProject) {
|
||||
const refetch = useRefetchActive()
|
||||
const refetch = useRefetchActive();
|
||||
const deletion = api.project.remove.useMutation({
|
||||
onSuccess() {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Project deleted successfully.',
|
||||
})
|
||||
refetch()
|
||||
}
|
||||
})
|
||||
});
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -42,7 +43,7 @@ export function ProjectActions({ id }: IProject) {
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
pushModal("EditProject", { id });
|
||||
pushModal('EditProject', { id });
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
@@ -57,9 +58,9 @@ export function ProjectActions({ id }: IProject) {
|
||||
onConfirm() {
|
||||
deletion.mutate({
|
||||
id,
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import { formatDate } from "@/utils/date";
|
||||
import { type ColumnDef } from "@tanstack/react-table";
|
||||
import { type Project as IProject } from "@prisma/client";
|
||||
import { ProjectActions } from "./ProjectActions";
|
||||
import { formatDate } from '@/utils/date';
|
||||
import type { Project as IProject } from '@prisma/client';
|
||||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
|
||||
import { ProjectActions } from './ProjectActions';
|
||||
|
||||
export type Project = IProject;
|
||||
|
||||
export const columns: ColumnDef<Project>[] = [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Name",
|
||||
accessorKey: 'name',
|
||||
header: 'Name',
|
||||
},
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: "Created at",
|
||||
accessorKey: 'createdAt',
|
||||
header: 'Created at',
|
||||
cell({ row }) {
|
||||
const date = row.original.createdAt;
|
||||
return <div>{formatDate(date)}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "Actions",
|
||||
id: 'actions',
|
||||
header: 'Actions',
|
||||
cell: ({ row }) => <ProjectActions {...row.original} />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useDispatch, useSelector } from "@/redux";
|
||||
import { RadioGroup, RadioGroupItem } from "../ui/radio-group";
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import type { IChartType } from '@/types';
|
||||
import { chartTypes } from '@/utils/constants';
|
||||
|
||||
import { Combobox } from '../ui/combobox';
|
||||
import { RadioGroup, RadioGroupItem } from '../ui/radio-group';
|
||||
import {
|
||||
changeChartType,
|
||||
changeDateRanges,
|
||||
changeInterval,
|
||||
} from "./reportSlice";
|
||||
import { Combobox } from "../ui/combobox";
|
||||
import { type IChartType } from "@/types";
|
||||
import { chartTypes } from "@/utils/constants";
|
||||
} from './reportSlice';
|
||||
|
||||
export function ReportChartType() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useDispatch, useSelector } from "@/redux";
|
||||
import { RadioGroup, RadioGroupItem } from "../ui/radio-group";
|
||||
import { changeDateRanges, changeInterval } from "./reportSlice";
|
||||
import { Combobox } from "../ui/combobox";
|
||||
import { type IInterval } from "@/types";
|
||||
import { intervals, timeRanges } from "@/utils/constants";
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import type { IInterval } from '@/types';
|
||||
import { intervals, timeRanges } from '@/utils/constants';
|
||||
|
||||
import { Combobox } from '../ui/combobox';
|
||||
import { RadioGroup, RadioGroupItem } from '../ui/radio-group';
|
||||
import { changeDateRanges, changeInterval } from './reportSlice';
|
||||
|
||||
export function ReportDateRange() {
|
||||
const dispatch = useDispatch();
|
||||
@@ -28,7 +29,7 @@ export function ReportDateRange() {
|
||||
);
|
||||
})}
|
||||
</RadioGroup>
|
||||
{chartType === "linear" && (
|
||||
{chartType === 'linear' && (
|
||||
<div className="w-full max-w-[200px]">
|
||||
<Combobox
|
||||
placeholder="Interval"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { pick } from "ramda";
|
||||
import { createContext, memo, useContext, useMemo } from "react";
|
||||
import { createContext, memo, useContext, useMemo } from 'react';
|
||||
import { pick } from 'ramda';
|
||||
|
||||
type ChartContextType = {
|
||||
interface ChartContextType {
|
||||
editMode: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
type ChartProviderProps = {
|
||||
children: React.ReactNode;
|
||||
@@ -20,7 +20,7 @@ export function ChartProvider({ children, editMode }: ChartProviderProps) {
|
||||
() => ({
|
||||
editMode,
|
||||
}),
|
||||
[editMode],
|
||||
[editMode]
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@@ -28,20 +28,24 @@ export function ChartProvider({ children, editMode }: ChartProviderProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function withChartProivder<ComponentProps>(WrappedComponent: React.FC<ComponentProps>) {
|
||||
export function withChartProivder<ComponentProps>(
|
||||
WrappedComponent: React.FC<ComponentProps>
|
||||
) {
|
||||
const WithChartProvider = (props: ComponentProps & ChartContextType) => {
|
||||
return (
|
||||
<ChartProvider {...props}>
|
||||
<WrappedComponent {...props} />
|
||||
</ChartProvider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
WithChartProvider.displayName = `WithChartProvider(${WrappedComponent.displayName ?? WrappedComponent.name ?? 'Component'})`
|
||||
WithChartProvider.displayName = `WithChartProvider(${
|
||||
WrappedComponent.displayName ?? WrappedComponent.name ?? 'Component'
|
||||
})`;
|
||||
|
||||
return memo(WithChartProvider)
|
||||
return memo(WithChartProvider);
|
||||
}
|
||||
|
||||
export function useChartContext() {
|
||||
return useContext(ChartContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
import { ColorSquare } from "@/components/ColorSquare";
|
||||
import { type IChartData } from "@/types";
|
||||
import { type RouterOutputs } from "@/utils/api";
|
||||
import { getChartColor } from "@/utils/theme";
|
||||
import { useMemo, useState } from 'react';
|
||||
import { ColorSquare } from '@/components/ColorSquare';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import type { IChartData } from '@/types';
|
||||
import type { RouterOutputs } from '@/utils/api';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
import {
|
||||
useReactTable,
|
||||
getCoreRowModel,
|
||||
flexRender,
|
||||
createColumnHelper,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
type SortingState,
|
||||
} from "@tanstack/react-table";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useElementSize } from "usehooks-ts";
|
||||
import { useChartContext } from "./ChartProvider";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { cn } from "@/utils/cn";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table';
|
||||
import type { SortingState } from '@tanstack/react-table';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { useElementSize } from 'usehooks-ts';
|
||||
|
||||
import { useChartContext } from './ChartProvider';
|
||||
|
||||
const columnHelper =
|
||||
createColumnHelper<RouterOutputs["chart"]["chart"]["series"][number]>();
|
||||
createColumnHelper<RouterOutputs['chart']['chart']['series'][number]>();
|
||||
|
||||
type ReportBarChartProps = {
|
||||
interface ReportBarChartProps {
|
||||
data: IChartData;
|
||||
};
|
||||
}
|
||||
|
||||
export function ReportBarChart({ data }: ReportBarChartProps) {
|
||||
const { editMode } = useChartContext();
|
||||
@@ -31,13 +39,13 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
||||
const table = useReactTable({
|
||||
data: useMemo(
|
||||
() => (editMode ? data.series : data.series.slice(0, 20)),
|
||||
[editMode, data],
|
||||
[editMode, data]
|
||||
),
|
||||
columns: useMemo(() => {
|
||||
return [
|
||||
columnHelper.accessor((row) => row.name, {
|
||||
id: "label",
|
||||
header: () => "Label",
|
||||
id: 'label',
|
||||
header: () => 'Label',
|
||||
cell(info) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -50,35 +58,35 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
||||
size: width ? width * 0.3 : undefined,
|
||||
}),
|
||||
columnHelper.accessor((row) => row.totalCount, {
|
||||
id: "totalCount",
|
||||
id: 'totalCount',
|
||||
cell: (info) => (
|
||||
<div className="text-right font-medium">{info.getValue()}</div>
|
||||
),
|
||||
header: () => "Count",
|
||||
header: () => 'Count',
|
||||
footer: (info) => info.column.id,
|
||||
size: width ? width * 0.1 : undefined,
|
||||
enableSorting: true,
|
||||
}),
|
||||
columnHelper.accessor((row) => row.totalCount, {
|
||||
id: "graph",
|
||||
id: 'graph',
|
||||
cell: (info) => (
|
||||
<div
|
||||
className="shine h-4 rounded [.mini_&]:h-3"
|
||||
style={{
|
||||
width:
|
||||
(info.getValue() / info.row.original.meta.highest) * 100 +
|
||||
"%",
|
||||
'%',
|
||||
background: getChartColor(info.row.index),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
header: () => "Graph",
|
||||
header: () => 'Graph',
|
||||
footer: (info) => info.column.id,
|
||||
size: width ? width * 0.6 : undefined,
|
||||
}),
|
||||
];
|
||||
}, [width]),
|
||||
columnResizeMode: "onChange",
|
||||
columnResizeMode: 'onChange',
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
@@ -100,7 +108,7 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
||||
<ColorSquare>{event.id}</ColorSquare> {event.name}
|
||||
</div>
|
||||
<div className="mt-6 font-mono text-5xl font-light">
|
||||
{new Intl.NumberFormat("en-IN", {
|
||||
{new Intl.NumberFormat('en-IN', {
|
||||
maximumSignificantDigits: 20,
|
||||
}).format(event.count)}
|
||||
</div>
|
||||
@@ -134,16 +142,16 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
||||
<div
|
||||
{...{
|
||||
className: cn(
|
||||
"flex items-center gap-2",
|
||||
'flex items-center gap-2',
|
||||
header.column.getCanSort() &&
|
||||
"cursor-pointer select-none",
|
||||
'cursor-pointer select-none'
|
||||
),
|
||||
onClick: header.column.getToggleSortingHandler(),
|
||||
}}
|
||||
>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
header.getContext()
|
||||
)}
|
||||
{{
|
||||
asc: <ChevronUp className="ml-auto" size={14} />,
|
||||
@@ -156,7 +164,7 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
||||
onMouseDown: header.getResizeHandler(),
|
||||
onTouchStart: header.getResizeHandler(),
|
||||
className: `resizer ${
|
||||
header.column.getIsResizing() ? "isResizing" : ""
|
||||
header.column.getIsResizing() ? 'isResizing' : ''
|
||||
}`,
|
||||
style: {},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { AutoSizer } from '@/components/AutoSizer';
|
||||
import { useFormatDateInterval } from '@/hooks/useFormatDateInterval';
|
||||
import type { IChartData, IInterval } from '@/types';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
import {
|
||||
CartesianGrid,
|
||||
Line,
|
||||
@@ -5,20 +10,16 @@ import {
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { ReportLineChartTooltip } from "./ReportLineChartTooltip";
|
||||
import { useFormatDateInterval } from "@/hooks/useFormatDateInterval";
|
||||
import { type IChartData, type IInterval } from "@/types";
|
||||
import { getChartColor } from "@/utils/theme";
|
||||
import { ReportTable } from "./ReportTable";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { AutoSizer } from "@/components/AutoSizer";
|
||||
import { useChartContext } from "./ChartProvider";
|
||||
} from 'recharts';
|
||||
|
||||
type ReportLineChartProps = {
|
||||
import { useChartContext } from './ChartProvider';
|
||||
import { ReportLineChartTooltip } from './ReportLineChartTooltip';
|
||||
import { ReportTable } from './ReportTable';
|
||||
|
||||
interface ReportLineChartProps {
|
||||
data: IChartData;
|
||||
interval: IInterval;
|
||||
};
|
||||
}
|
||||
|
||||
export function ReportLineChart({ interval, data }: ReportLineChartProps) {
|
||||
const { editMode } = useChartContext();
|
||||
@@ -31,7 +32,7 @@ export function ReportLineChart({ interval, data }: ReportLineChartProps) {
|
||||
const max = 20;
|
||||
|
||||
setVisibleSeries(
|
||||
data?.series?.slice(0, max).map((serie) => serie.name) ?? [],
|
||||
data?.series?.slice(0, max).map((serie) => serie.name) ?? []
|
||||
);
|
||||
// ref.current = true;
|
||||
}
|
||||
@@ -42,7 +43,7 @@ export function ReportLineChart({ interval, data }: ReportLineChartProps) {
|
||||
<AutoSizer disableHeight>
|
||||
{({ width }) => (
|
||||
<LineChart width={width} height={Math.min(width * 0.5, 400)}>
|
||||
<YAxis dataKey={"count"} width={30} fontSize={12}></YAxis>
|
||||
<YAxis dataKey={'count'} width={30} fontSize={12}></YAxis>
|
||||
<Tooltip content={<ReportLineChartTooltip />} />
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
@@ -60,7 +61,7 @@ export function ReportLineChart({ interval, data }: ReportLineChartProps) {
|
||||
})
|
||||
.map((serie) => {
|
||||
const realIndex = data?.series.findIndex(
|
||||
(item) => item.name === serie.name,
|
||||
(item) => item.name === serie.name
|
||||
);
|
||||
const key = serie.name;
|
||||
const strokeColor = getChartColor(realIndex);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useFormatDateInterval } from "@/hooks/useFormatDateInterval";
|
||||
import { useMappings } from "@/hooks/useMappings";
|
||||
import { useSelector } from "@/redux";
|
||||
import { type IToolTipProps } from "@/types";
|
||||
import { useFormatDateInterval } from '@/hooks/useFormatDateInterval';
|
||||
import { useMappings } from '@/hooks/useMappings';
|
||||
import { useSelector } from '@/redux';
|
||||
import type { IToolTipProps } from '@/types';
|
||||
|
||||
type ReportLineChartTooltipProps = IToolTipProps<{
|
||||
color: string;
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import * as React from "react";
|
||||
import { useFormatDateInterval } from "@/hooks/useFormatDateInterval";
|
||||
import { useSelector } from "@/redux";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { getChartColor } from "@/utils/theme";
|
||||
import { cn } from "@/utils/cn";
|
||||
import { useMappings } from "@/hooks/useMappings";
|
||||
import { type IChartData } from "@/types";
|
||||
import * as React from 'react';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { useFormatDateInterval } from '@/hooks/useFormatDateInterval';
|
||||
import { useMappings } from '@/hooks/useMappings';
|
||||
import { useSelector } from '@/redux';
|
||||
import type { IChartData } from '@/types';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
|
||||
|
||||
type ReportTableProps = {
|
||||
interface ReportTableProps {
|
||||
data: IChartData;
|
||||
visibleSeries: string[];
|
||||
setVisibleSeries: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
};
|
||||
}
|
||||
|
||||
export function ReportTable({
|
||||
data,
|
||||
@@ -21,7 +20,7 @@ export function ReportTable({
|
||||
}: ReportTableProps) {
|
||||
const interval = useSelector((state) => state.report.interval);
|
||||
const formatDate = useFormatDateInterval(interval);
|
||||
const getLabel = useMappings()
|
||||
const getLabel = useMappings();
|
||||
|
||||
function handleChange(name: string, checked: boolean) {
|
||||
setVisibleSeries((prev) => {
|
||||
@@ -33,11 +32,12 @@ export function ReportTable({
|
||||
});
|
||||
}
|
||||
|
||||
const row = "flex border-b border-border last:border-b-0 flex-1";
|
||||
const cell = "p-2 last:pr-8 last:w-[8rem]";
|
||||
const value = "min-w-[6rem] text-right";
|
||||
const header = "text-sm font-medium";
|
||||
const total = 'bg-gray-50 text-emerald-600 font-medium border-r border-border'
|
||||
const row = 'flex border-b border-border last:border-b-0 flex-1';
|
||||
const cell = 'p-2 last:pr-8 last:w-[8rem]';
|
||||
const value = 'min-w-[6rem] text-right';
|
||||
const header = 'text-sm font-medium';
|
||||
const total =
|
||||
'bg-gray-50 text-emerald-600 font-medium border-r border-border';
|
||||
return (
|
||||
<div className="flex w-fit max-w-full rounded-md border border-border">
|
||||
{/* Labels */}
|
||||
@@ -49,7 +49,7 @@ export function ReportTable({
|
||||
return (
|
||||
<div
|
||||
key={serie.name}
|
||||
className={cn("flex max-w-[200px] items-center gap-2", row, cell)}
|
||||
className={cn('flex max-w-[200px] items-center gap-2', row, cell)}
|
||||
>
|
||||
<Checkbox
|
||||
onCheckedChange={(checked) =>
|
||||
@@ -76,7 +76,7 @@ export function ReportTable({
|
||||
{/* ScrollView for all values */}
|
||||
<div className="w-full overflow-auto">
|
||||
{/* Header */}
|
||||
<div className={cn("w-max", row)}>
|
||||
<div className={cn('w-max', row)}>
|
||||
<div className={cn(header, value, cell, total)}>Total</div>
|
||||
{data.series[0]?.data.map((serie) => (
|
||||
<div
|
||||
@@ -91,8 +91,10 @@ export function ReportTable({
|
||||
{/* Values */}
|
||||
{data.series.map((serie) => {
|
||||
return (
|
||||
<div className={cn("w-max", row)} key={serie.name}>
|
||||
<div className={cn(header, value, cell, total)}>{serie.totalCount}</div>
|
||||
<div className={cn('w-max', row)} key={serie.name}>
|
||||
<div className={cn(header, value, cell, total)}>
|
||||
{serie.totalCount}
|
||||
</div>
|
||||
{serie.data.map((item) => {
|
||||
return (
|
||||
<div key={item.date} className={cn(value, cell)}>
|
||||
|
||||
@@ -1,64 +1,67 @@
|
||||
import { api } from "@/utils/api";
|
||||
import { type IChartInput } from "@/types";
|
||||
import { ReportBarChart } from "./ReportBarChart";
|
||||
import { ReportLineChart } from "./ReportLineChart";
|
||||
import { withChartProivder } from "./ChartProvider";
|
||||
import type { IChartInput } from '@/types';
|
||||
import { api } from '@/utils/api';
|
||||
|
||||
type ReportLineChartProps = IChartInput
|
||||
import { withChartProivder } from './ChartProvider';
|
||||
import { ReportBarChart } from './ReportBarChart';
|
||||
import { ReportLineChart } from './ReportLineChart';
|
||||
|
||||
export const Chart = withChartProivder(({
|
||||
interval,
|
||||
events,
|
||||
breakdowns,
|
||||
chartType,
|
||||
name,
|
||||
range,
|
||||
}: ReportLineChartProps) => {
|
||||
const hasEmptyFilters = events.some((event) => event.filters.some((filter) => filter.value.length === 0));
|
||||
const chart = api.chart.chart.useQuery(
|
||||
{
|
||||
interval,
|
||||
chartType,
|
||||
events,
|
||||
breakdowns,
|
||||
name,
|
||||
range,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
enabled: events.length > 0 && !hasEmptyFilters,
|
||||
},
|
||||
);
|
||||
type ReportLineChartProps = IChartInput;
|
||||
|
||||
const anyData = Boolean(chart.data?.series?.[0]?.data)
|
||||
export const Chart = withChartProivder(
|
||||
({
|
||||
interval,
|
||||
events,
|
||||
breakdowns,
|
||||
chartType,
|
||||
name,
|
||||
range,
|
||||
}: ReportLineChartProps) => {
|
||||
const hasEmptyFilters = events.some((event) =>
|
||||
event.filters.some((filter) => filter.value.length === 0)
|
||||
);
|
||||
const chart = api.chart.chart.useQuery(
|
||||
{
|
||||
interval,
|
||||
chartType,
|
||||
events,
|
||||
breakdowns,
|
||||
name,
|
||||
range,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
enabled: events.length > 0 && !hasEmptyFilters,
|
||||
}
|
||||
);
|
||||
|
||||
if(chart.isFetching && !anyData) {
|
||||
return (<p>Loading...</p>)
|
||||
const anyData = Boolean(chart.data?.series?.[0]?.data);
|
||||
|
||||
if (chart.isFetching && !anyData) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
if (chart.isError) {
|
||||
return <p>Error</p>;
|
||||
}
|
||||
|
||||
if (!chart.isSuccess) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
if (!anyData) {
|
||||
return <p>No data</p>;
|
||||
}
|
||||
|
||||
if (chartType === 'bar') {
|
||||
return <ReportBarChart data={chart.data} />;
|
||||
}
|
||||
|
||||
if (chartType === 'linear') {
|
||||
return <ReportLineChart interval={interval} data={chart.data} />;
|
||||
}
|
||||
|
||||
return <p>Chart type "{chartType}" is not supported yet.</p>;
|
||||
}
|
||||
|
||||
if(chart.isError) {
|
||||
return (<p>Error</p>)
|
||||
}
|
||||
|
||||
if(!chart.isSuccess) {
|
||||
return (<p>Loading...</p>)
|
||||
}
|
||||
|
||||
|
||||
if(!anyData) {
|
||||
return (<p>No data</p>)
|
||||
}
|
||||
|
||||
if(chartType === 'bar') {
|
||||
return <ReportBarChart data={chart.data} />
|
||||
}
|
||||
|
||||
if(chartType === 'linear') {
|
||||
return <ReportLineChart interval={interval} data={chart.data} />
|
||||
}
|
||||
|
||||
|
||||
return <p>Chart type "{chartType}" is not supported yet.</p>
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import {
|
||||
type IChartBreakdown,
|
||||
type IChartEvent,
|
||||
type IChartInput,
|
||||
type IChartRange,
|
||||
type IChartType,
|
||||
type IInterval,
|
||||
import type {
|
||||
IChartBreakdown,
|
||||
IChartEvent,
|
||||
IChartInput,
|
||||
IChartRange,
|
||||
IChartType,
|
||||
IInterval,
|
||||
} from '@/types';
|
||||
import { alphabetIds } from '@/utils/constants';
|
||||
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
type InitialState = IChartInput & {
|
||||
startDate: string | null;
|
||||
|
||||
@@ -1,48 +1,41 @@
|
||||
import * as React from "react"
|
||||
import { Filter, MoreHorizontal, Tags, Trash } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command"
|
||||
import * as React from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
|
||||
export type ReportBreakdownMoreProps = {
|
||||
onClick: (action: 'remove') => void
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { MoreHorizontal, Trash } from 'lucide-react';
|
||||
|
||||
export interface ReportBreakdownMoreProps {
|
||||
onClick: (action: 'remove') => void;
|
||||
}
|
||||
|
||||
export function ReportBreakdownMore({ onClick }: ReportBreakdownMoreProps) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-[200px]">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem className="text-red-600" onClick={() => onClick('remove')}>
|
||||
<Trash className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-[200px]">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
className="text-red-600"
|
||||
onClick={() => onClick('remove')}
|
||||
>
|
||||
<Trash className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { api } from "@/utils/api";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { useDispatch, useSelector } from "@/redux";
|
||||
import { addBreakdown, changeBreakdown, removeBreakdown } from "../reportSlice";
|
||||
import { type ReportEventMoreProps } from "./ReportEventMore";
|
||||
import { type IChartBreakdown } from "@/types";
|
||||
import { ReportBreakdownMore } from "./ReportBreakdownMore";
|
||||
import { RenderDots } from "@/components/ui/RenderDots";
|
||||
import { ColorSquare } from "@/components/ColorSquare";
|
||||
import { ColorSquare } from '@/components/ColorSquare';
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { RenderDots } from '@/components/ui/RenderDots';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import type { IChartBreakdown } from '@/types';
|
||||
import { api } from '@/utils/api';
|
||||
|
||||
import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice';
|
||||
import { ReportBreakdownMore } from './ReportBreakdownMore';
|
||||
import type { ReportEventMoreProps } from './ReportEventMore';
|
||||
|
||||
export function ReportBreakdowns() {
|
||||
const selectedBreakdowns = useSelector((state) => state.report.breakdowns);
|
||||
@@ -18,9 +19,9 @@ export function ReportBreakdowns() {
|
||||
}));
|
||||
|
||||
const handleMore = (breakdown: IChartBreakdown) => {
|
||||
const callback: ReportEventMoreProps["onClick"] = (action) => {
|
||||
const callback: ReportEventMoreProps['onClick'] = (action) => {
|
||||
switch (action) {
|
||||
case "remove": {
|
||||
case 'remove': {
|
||||
return dispatch(removeBreakdown(breakdown));
|
||||
}
|
||||
}
|
||||
@@ -37,9 +38,7 @@ export function ReportBreakdowns() {
|
||||
return (
|
||||
<div key={item.name} className="rounded-lg border">
|
||||
<div className="flex items-center gap-2 p-2 px-4">
|
||||
<ColorSquare>
|
||||
{index}
|
||||
</ColorSquare>
|
||||
<ColorSquare>{index}</ColorSquare>
|
||||
<Combobox
|
||||
value={item.name}
|
||||
onChange={(value) => {
|
||||
@@ -47,7 +46,7 @@ export function ReportBreakdowns() {
|
||||
changeBreakdown({
|
||||
...item,
|
||||
name: value,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}}
|
||||
items={propertiesCombobox}
|
||||
@@ -61,12 +60,12 @@ export function ReportBreakdowns() {
|
||||
|
||||
{selectedBreakdowns.length === 0 && (
|
||||
<Combobox
|
||||
value={""}
|
||||
value={''}
|
||||
onChange={(value) => {
|
||||
dispatch(
|
||||
addBreakdown({
|
||||
name: value,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}}
|
||||
items={propertiesCombobox}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { api } from "@/utils/api";
|
||||
import {
|
||||
type IChartEvent,
|
||||
type IChartEventFilterValue,
|
||||
type IChartEventFilter,
|
||||
} from "@/types";
|
||||
import { CreditCard, SlidersHorizontal, Trash } from "lucide-react";
|
||||
import type { Dispatch } from 'react';
|
||||
import { ColorSquare } from '@/components/ColorSquare';
|
||||
import { Dropdown } from '@/components/Dropdown';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ComboboxMulti } from '@/components/ui/combobox-multi';
|
||||
import {
|
||||
CommandDialog,
|
||||
CommandEmpty,
|
||||
@@ -13,23 +11,26 @@ import {
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "@/components/ui/command";
|
||||
import { type Dispatch } from "react";
|
||||
import { RenderDots } from "@/components/ui/RenderDots";
|
||||
import { useDispatch } from "@/redux";
|
||||
import { changeEvent } from "../reportSlice";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ComboboxMulti } from "@/components/ui/combobox-multi";
|
||||
import { Dropdown } from "@/components/Dropdown";
|
||||
import { operators } from "@/utils/constants";
|
||||
import { useMappings } from "@/hooks/useMappings";
|
||||
import { ColorSquare } from "@/components/ColorSquare";
|
||||
} from '@/components/ui/command';
|
||||
import { RenderDots } from '@/components/ui/RenderDots';
|
||||
import { useMappings } from '@/hooks/useMappings';
|
||||
import { useDispatch } from '@/redux';
|
||||
import type {
|
||||
IChartEvent,
|
||||
IChartEventFilter,
|
||||
IChartEventFilterValue,
|
||||
} from '@/types';
|
||||
import { api } from '@/utils/api';
|
||||
import { operators } from '@/utils/constants';
|
||||
import { CreditCard, SlidersHorizontal, Trash } from 'lucide-react';
|
||||
|
||||
type ReportEventFiltersProps = {
|
||||
import { changeEvent } from '../reportSlice';
|
||||
|
||||
interface ReportEventFiltersProps {
|
||||
event: IChartEvent;
|
||||
isCreating: boolean;
|
||||
setIsCreating: Dispatch<boolean>;
|
||||
};
|
||||
}
|
||||
|
||||
export function ReportEventFilters({
|
||||
event,
|
||||
@@ -43,7 +44,7 @@ export function ReportEventFilters({
|
||||
},
|
||||
{
|
||||
enabled: !!event.name,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -71,11 +72,11 @@ export function ReportEventFilters({
|
||||
{
|
||||
id: (event.filters.length + 1).toString(),
|
||||
name: item,
|
||||
operator: "is",
|
||||
operator: 'is',
|
||||
value: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
@@ -92,13 +93,13 @@ export function ReportEventFilters({
|
||||
);
|
||||
}
|
||||
|
||||
type FilterProps = {
|
||||
interface FilterProps {
|
||||
event: IChartEvent;
|
||||
filter: IChartEvent["filters"][number];
|
||||
};
|
||||
filter: IChartEvent['filters'][number];
|
||||
}
|
||||
|
||||
function Filter({ filter, event }: FilterProps) {
|
||||
const getLabel = useMappings()
|
||||
const getLabel = useMappings();
|
||||
const dispatch = useDispatch();
|
||||
const potentialValues = api.chart.values.useQuery({
|
||||
event: event.name,
|
||||
@@ -116,12 +117,12 @@ function Filter({ filter, event }: FilterProps) {
|
||||
changeEvent({
|
||||
...event,
|
||||
filters: event.filters.filter((item) => item.id !== filter.id),
|
||||
}),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const changeFilterValue = (
|
||||
value: IChartEventFilterValue | IChartEventFilterValue[],
|
||||
value: IChartEventFilterValue | IChartEventFilterValue[]
|
||||
) => {
|
||||
dispatch(
|
||||
changeEvent({
|
||||
@@ -136,11 +137,11 @@ function Filter({ filter, event }: FilterProps) {
|
||||
|
||||
return item;
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const changeFilterOperator = (operator: IChartEventFilter["operator"]) => {
|
||||
const changeFilterOperator = (operator: IChartEventFilter['operator']) => {
|
||||
dispatch(
|
||||
changeEvent({
|
||||
...event,
|
||||
@@ -154,7 +155,7 @@ function Filter({ filter, event }: FilterProps) {
|
||||
|
||||
return item;
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@@ -168,7 +169,7 @@ function Filter({ filter, event }: FilterProps) {
|
||||
<SlidersHorizontal size={10} />
|
||||
</ColorSquare>
|
||||
<div className="flex flex-1 text-sm">
|
||||
<RenderDots truncate>{filter.name}</RenderDots>
|
||||
<RenderDots truncate>{filter.name}</RenderDots>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={removeFilter}>
|
||||
<Trash size={16} />
|
||||
@@ -178,12 +179,12 @@ function Filter({ filter, event }: FilterProps) {
|
||||
<Dropdown
|
||||
onChange={changeFilterOperator}
|
||||
items={Object.entries(operators).map(([key, value]) => ({
|
||||
value: key as IChartEventFilter["operator"],
|
||||
value: key as IChartEventFilter['operator'],
|
||||
label: value,
|
||||
}))}
|
||||
label="Segment"
|
||||
>
|
||||
<Button variant={"ghost"} className="whitespace-nowrap">
|
||||
<Button variant={'ghost'} className="whitespace-nowrap">
|
||||
{operators[filter.operator]}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
@@ -191,16 +192,16 @@ function Filter({ filter, event }: FilterProps) {
|
||||
placeholder="Select values"
|
||||
items={valuesCombobox}
|
||||
selected={filter.value.map((item) => ({
|
||||
value: item?.toString() ?? "__filter_value_null__",
|
||||
label: getLabel(item?.toString() ?? "__filter_value_null__"),
|
||||
value: item?.toString() ?? '__filter_value_null__',
|
||||
label: getLabel(item?.toString() ?? '__filter_value_null__'),
|
||||
}))}
|
||||
setSelected={(setFn) => {
|
||||
if(typeof setFn === "function") {
|
||||
if (typeof setFn === 'function') {
|
||||
const newValues = setFn(
|
||||
filter.value.map((item) => ({
|
||||
value: item?.toString() ?? "__filter_value_null__",
|
||||
label: getLabel(item?.toString() ?? "__filter_value_null__"),
|
||||
})),
|
||||
value: item?.toString() ?? '__filter_value_null__',
|
||||
label: getLabel(item?.toString() ?? '__filter_value_null__'),
|
||||
}))
|
||||
);
|
||||
changeFilterValue(newValues.map((item) => item.value));
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import * as React from "react"
|
||||
import { Filter, MoreHorizontal, Tags, Trash } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import * as React from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
@@ -9,7 +7,7 @@ import {
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command"
|
||||
} from '@/components/ui/command';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -22,47 +20,50 @@ import {
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Filter, MoreHorizontal, Tags, Trash } from 'lucide-react';
|
||||
|
||||
const labels = [
|
||||
"feature",
|
||||
"bug",
|
||||
"enhancement",
|
||||
"documentation",
|
||||
"design",
|
||||
"question",
|
||||
"maintenance",
|
||||
]
|
||||
|
||||
export type ReportEventMoreProps = {
|
||||
onClick: (action: 'createFilter' | 'remove') => void
|
||||
'feature',
|
||||
'bug',
|
||||
'enhancement',
|
||||
'documentation',
|
||||
'design',
|
||||
'question',
|
||||
'maintenance',
|
||||
];
|
||||
|
||||
export interface ReportEventMoreProps {
|
||||
onClick: (action: 'createFilter' | 'remove') => void;
|
||||
}
|
||||
|
||||
export function ReportEventMore({ onClick }: ReportEventMoreProps) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
return (
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem onClick={() => onClick('createFilter')}>
|
||||
<Filter className="mr-2 h-4 w-4" />
|
||||
Add filter
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="text-red-600" onClick={() => onClick('remove')}>
|
||||
<Trash className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
return (
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem onClick={() => onClick('createFilter')}>
|
||||
<Filter className="mr-2 h-4 w-4" />
|
||||
Add filter
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
className="text-red-600"
|
||||
onClick={() => onClick('remove')}
|
||||
>
|
||||
<Trash className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { api } from "@/utils/api";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { useDispatch, useSelector } from "@/redux";
|
||||
import { addEvent, changeEvent, removeEvent } from "../reportSlice";
|
||||
import { ReportEventFilters } from "./ReportEventFilters";
|
||||
import { useState } from "react";
|
||||
import { ReportEventMore, type ReportEventMoreProps } from "./ReportEventMore";
|
||||
import { type IChartEvent } from "@/types";
|
||||
import { Filter, GanttChart, Users } from "lucide-react";
|
||||
import { Dropdown } from "@/components/Dropdown";
|
||||
import { ColorSquare } from "@/components/ColorSquare";
|
||||
import { useState } from 'react';
|
||||
import { ColorSquare } from '@/components/ColorSquare';
|
||||
import { Dropdown } from '@/components/Dropdown';
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import type { IChartEvent } from '@/types';
|
||||
import { api } from '@/utils/api';
|
||||
import { Filter, GanttChart, Users } from 'lucide-react';
|
||||
|
||||
import { addEvent, changeEvent, removeEvent } from '../reportSlice';
|
||||
import { ReportEventFilters } from './ReportEventFilters';
|
||||
import { ReportEventMore } from './ReportEventMore';
|
||||
import type { ReportEventMoreProps } from './ReportEventMore';
|
||||
|
||||
export function ReportEvents() {
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
@@ -21,12 +23,12 @@ export function ReportEvents() {
|
||||
}));
|
||||
|
||||
const handleMore = (event: IChartEvent) => {
|
||||
const callback: ReportEventMoreProps["onClick"] = (action) => {
|
||||
const callback: ReportEventMoreProps['onClick'] = (action) => {
|
||||
switch (action) {
|
||||
case "createFilter": {
|
||||
case 'createFilter': {
|
||||
return setIsCreating(true);
|
||||
}
|
||||
case "remove": {
|
||||
case 'remove': {
|
||||
return dispatch(removeEvent(event));
|
||||
}
|
||||
}
|
||||
@@ -43,9 +45,7 @@ export function ReportEvents() {
|
||||
return (
|
||||
<div key={event.name} className="rounded-lg border">
|
||||
<div className="flex items-center gap-2 p-2">
|
||||
<ColorSquare>
|
||||
{event.id}
|
||||
</ColorSquare>
|
||||
<ColorSquare>{event.id}</ColorSquare>
|
||||
<Combobox
|
||||
value={event.name}
|
||||
onChange={(value) => {
|
||||
@@ -54,7 +54,7 @@ export function ReportEvents() {
|
||||
...event,
|
||||
name: value,
|
||||
filters: [],
|
||||
}),
|
||||
})
|
||||
);
|
||||
}}
|
||||
items={eventsCombobox}
|
||||
@@ -71,32 +71,36 @@ export function ReportEvents() {
|
||||
changeEvent({
|
||||
...event,
|
||||
segment,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
value: "event",
|
||||
label: "All events",
|
||||
value: 'event',
|
||||
label: 'All events',
|
||||
},
|
||||
{
|
||||
value: "user",
|
||||
label: "Unique users",
|
||||
value: 'user',
|
||||
label: 'Unique users',
|
||||
},
|
||||
]}
|
||||
label="Segment"
|
||||
>
|
||||
<button className="flex items-center gap-1 rounded-md border border-border p-1 px-2 font-medium leading-none text-xs">
|
||||
{event.segment === "user" ? (
|
||||
<><Users size={12} /> Unique users</>
|
||||
) : (
|
||||
<><GanttChart size={12} /> All events</>
|
||||
{event.segment === 'user' ? (
|
||||
<>
|
||||
<Users size={12} /> Unique users
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<GanttChart size={12} /> All events
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</Dropdown>
|
||||
<button
|
||||
onClick={() => {
|
||||
handleMore(event)("createFilter");
|
||||
handleMore(event)('createFilter');
|
||||
}}
|
||||
className="flex items-center gap-1 rounded-md border border-border p-1 px-2 font-medium leading-none text-xs"
|
||||
>
|
||||
@@ -111,14 +115,14 @@ export function ReportEvents() {
|
||||
})}
|
||||
|
||||
<Combobox
|
||||
value={""}
|
||||
value={''}
|
||||
onChange={(value) => {
|
||||
dispatch(
|
||||
addEvent({
|
||||
name: value,
|
||||
segment: "event",
|
||||
segment: 'event',
|
||||
filters: [],
|
||||
}),
|
||||
})
|
||||
);
|
||||
}}
|
||||
items={eventsCombobox}
|
||||
|
||||
@@ -1,39 +1,47 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useReportId } from "../hooks/useReportId";
|
||||
import { api, handleError } from "@/utils/api";
|
||||
import { useSelector } from "@/redux";
|
||||
import { pushModal } from "@/modals";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { pushModal } from '@/modals';
|
||||
import { useSelector } from '@/redux';
|
||||
import { api, handleError } from '@/utils/api';
|
||||
|
||||
import { useReportId } from '../hooks/useReportId';
|
||||
|
||||
export function ReportSaveButton() {
|
||||
const { reportId } = useReportId();
|
||||
const update = api.report.update.useMutation({
|
||||
onSuccess() {
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Report updated.",
|
||||
title: 'Success',
|
||||
description: 'Report updated.',
|
||||
});
|
||||
},
|
||||
onError: handleError
|
||||
onError: handleError,
|
||||
});
|
||||
const report = useSelector((state) => state.report);
|
||||
|
||||
if (reportId) {
|
||||
return <Button loading={update.isLoading} onClick={() => {
|
||||
update.mutate({
|
||||
reportId,
|
||||
report,
|
||||
dashboardId: "9227feb4-ad59-40f3-b887-3501685733dd",
|
||||
projectId: "f7eabf0c-e0b0-4ac0-940f-1589715b0c3d",
|
||||
});
|
||||
}}>Update</Button>;
|
||||
return (
|
||||
<Button
|
||||
loading={update.isLoading}
|
||||
onClick={() => {
|
||||
update.mutate({
|
||||
reportId,
|
||||
report,
|
||||
dashboardId: '9227feb4-ad59-40f3-b887-3501685733dd',
|
||||
projectId: 'f7eabf0c-e0b0-4ac0-940f-1589715b0c3d',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
pushModal('SaveReport', {
|
||||
report,
|
||||
})
|
||||
});
|
||||
}}
|
||||
>
|
||||
Create
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReportEvents } from "./ReportEvents";
|
||||
import { ReportBreakdowns } from "./ReportBreakdowns";
|
||||
import { ReportSaveButton } from "./ReportSaveButton";
|
||||
import { ReportBreakdowns } from './ReportBreakdowns';
|
||||
import { ReportEvents } from './ReportEvents';
|
||||
import { ReportSaveButton } from './ReportSaveButton';
|
||||
|
||||
export function ReportSidebar() {
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { cn } from "@/utils/cn";
|
||||
import { Asterisk, ChevronRight } from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
||||
import { cn } from '@/utils/cn';
|
||||
import { Asterisk, ChevronRight } from 'lucide-react';
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip';
|
||||
|
||||
interface RenderDotsProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: string;
|
||||
@@ -13,27 +14,27 @@ export function RenderDots({
|
||||
truncate,
|
||||
...props
|
||||
}: RenderDotsProps) {
|
||||
const parts = children.split(".");
|
||||
const parts = children.split('.');
|
||||
const sliceAt = truncate && parts.length > 3 ? 3 : 0;
|
||||
return (
|
||||
<Tooltip disableHoverableContent={true} open={sliceAt === 0 ? false : undefined}>
|
||||
<Tooltip
|
||||
disableHoverableContent={true}
|
||||
open={sliceAt === 0 ? false : undefined}
|
||||
>
|
||||
<TooltipTrigger>
|
||||
<div
|
||||
{...props}
|
||||
className={cn("flex items-center gap-1", className)}
|
||||
>
|
||||
<div {...props} className={cn('flex items-center gap-1', className)}>
|
||||
{parts.slice(-sliceAt).map((str, index) => {
|
||||
return (
|
||||
<div className="flex items-center gap-1" key={str + index}>
|
||||
{index !== 0 && (
|
||||
<ChevronRight className="relative top-[0.9px] !h-3 !w-3 flex-shrink-0" />
|
||||
)}
|
||||
{str.includes("[*]") ? (
|
||||
{str.includes('[*]') ? (
|
||||
<>
|
||||
{str.replace("[*]", "")}
|
||||
{str.replace('[*]', '')}
|
||||
<Asterisk className="relative top-[0.9px] !h-3 !w-3 flex-shrink-0" />
|
||||
</>
|
||||
) : str === "*" ? (
|
||||
) : str === '*' ? (
|
||||
<Asterisk className="relative top-[0.9px] !h-3 !w-3 flex-shrink-0" />
|
||||
) : (
|
||||
str
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import * as React from "react"
|
||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||
import * as React from 'react';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { cn } from '@/utils/cn';
|
||||
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
const AlertDialog = AlertDialogPrimitive.Root;
|
||||
|
||||
const AlertDialog = AlertDialogPrimitive.Root
|
||||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
||||
|
||||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||
|
||||
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
||||
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
||||
|
||||
const AlertDialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||
@@ -16,14 +15,14 @@ const AlertDialogOverlay = React.forwardRef<
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||
));
|
||||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
||||
|
||||
const AlertDialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||
@@ -34,14 +33,14 @@ const AlertDialogContent = React.forwardRef<
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
))
|
||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||
));
|
||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
||||
|
||||
const AlertDialogHeader = ({
|
||||
className,
|
||||
@@ -49,13 +48,13 @@ const AlertDialogHeader = ({
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
'flex flex-col space-y-2 text-center sm:text-left',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
AlertDialogHeader.displayName = "AlertDialogHeader"
|
||||
);
|
||||
AlertDialogHeader.displayName = 'AlertDialogHeader';
|
||||
|
||||
const AlertDialogFooter = ({
|
||||
className,
|
||||
@@ -63,13 +62,13 @@ const AlertDialogFooter = ({
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
AlertDialogFooter.displayName = "AlertDialogFooter"
|
||||
);
|
||||
AlertDialogFooter.displayName = 'AlertDialogFooter';
|
||||
|
||||
const AlertDialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||
@@ -77,11 +76,11 @@ const AlertDialogTitle = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold", className)}
|
||||
className={cn('text-lg font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||
));
|
||||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
|
||||
|
||||
const AlertDialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||
@@ -89,12 +88,12 @@ const AlertDialogDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
className={cn('text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
));
|
||||
AlertDialogDescription.displayName =
|
||||
AlertDialogPrimitive.Description.displayName
|
||||
AlertDialogPrimitive.Description.displayName;
|
||||
|
||||
const AlertDialogAction = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||
@@ -105,8 +104,8 @@ const AlertDialogAction = React.forwardRef<
|
||||
className={cn(buttonVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||
));
|
||||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
|
||||
|
||||
const AlertDialogCancel = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||
@@ -115,14 +114,14 @@ const AlertDialogCancel = React.forwardRef<
|
||||
<AlertDialogPrimitive.Cancel
|
||||
ref={ref}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"mt-2 sm:mt-0",
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'mt-2 sm:mt-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||
));
|
||||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
@@ -136,4 +135,4 @@ export {
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
|
||||
import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio';
|
||||
|
||||
const AspectRatio = AspectRatioPrimitive.Root
|
||||
const AspectRatio = AspectRatioPrimitive.Root;
|
||||
|
||||
export { AspectRatio }
|
||||
export { AspectRatio };
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
@@ -10,13 +9,13 @@ const Avatar = React.forwardRef<
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex h-9 w-9 shrink-0 overflow-hidden rounded-full",
|
||||
'relative flex h-9 w-9 shrink-0 overflow-hidden rounded-full',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Avatar.displayName = AvatarPrimitive.Root.displayName
|
||||
));
|
||||
Avatar.displayName = AvatarPrimitive.Root.displayName;
|
||||
|
||||
const AvatarImage = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||
@@ -24,11 +23,11 @@ const AvatarImage = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
className={cn('aspect-square h-full w-full', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
||||
));
|
||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
||||
|
||||
const AvatarFallback = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||
@@ -37,12 +36,12 @@ const AvatarFallback = React.forwardRef<
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-primary text-white",
|
||||
'flex h-full w-full items-center justify-center rounded-full bg-primary text-white',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
||||
));
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
||||
export { Avatar, AvatarImage, AvatarFallback };
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
||||
outline: 'text-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
variant: 'default',
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
@@ -30,7 +30,7 @@ export interface BadgeProps
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
export { Badge, badgeVariants };
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
interface ButtonProps
|
||||
@@ -43,10 +43,19 @@ interface ButtonProps
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
{ className, variant, size, asChild = false, children, loading, disabled, ...props },
|
||||
ref,
|
||||
{
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
children,
|
||||
loading,
|
||||
disabled,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
@@ -57,11 +66,11 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
{loading ? <Loader2 className="animate-spin" /> : <>{children}</>}
|
||||
</Comp>
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
Button.displayName = 'Button';
|
||||
Button.defaultProps = {
|
||||
type: "button",
|
||||
type: 'button',
|
||||
};
|
||||
|
||||
export { Button, buttonVariants };
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { Check } from "lucide-react"
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||
import { Check } from 'lucide-react';
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
@@ -11,18 +10,18 @@ const Checkbox = React.forwardRef<
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
className={cn('flex items-center justify-center text-current')}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
))
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||
));
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
||||
|
||||
export { Checkbox }
|
||||
export { Checkbox };
|
||||
|
||||
@@ -1,58 +1,62 @@
|
||||
import * as React from "react";
|
||||
import { X } from "lucide-react";
|
||||
import * as React from 'react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Command, CommandGroup, CommandItem } from '@/components/ui/command';
|
||||
import { Command as CommandPrimitive } from 'cmdk';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Command,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import { Command as CommandPrimitive } from "cmdk";
|
||||
type Item = Record<'value' | 'label', string>;
|
||||
|
||||
type Item = Record<"value" | "label", string>;
|
||||
|
||||
type ComboboxMultiProps = {
|
||||
interface ComboboxMultiProps {
|
||||
selected: Item[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<Item[]>>;
|
||||
items: Item[];
|
||||
placeholder: string
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
export function ComboboxMulti({ items, selected, setSelected, placeholder, ...props }: ComboboxMultiProps) {
|
||||
export function ComboboxMulti({
|
||||
items,
|
||||
selected,
|
||||
setSelected,
|
||||
placeholder,
|
||||
...props
|
||||
}: ComboboxMultiProps) {
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [inputValue, setInputValue] = React.useState("");
|
||||
const [inputValue, setInputValue] = React.useState('');
|
||||
|
||||
const handleUnselect = React.useCallback((item: Item) => {
|
||||
setSelected(prev => prev.filter(s => s.value !== item.value));
|
||||
setSelected((prev) => prev.filter((s) => s.value !== item.value));
|
||||
}, []);
|
||||
|
||||
const handleKeyDown = React.useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
const input = inputRef.current
|
||||
if (input) {
|
||||
if (e.key === "Delete" || e.key === "Backspace") {
|
||||
if (input.value === "") {
|
||||
setSelected(prev => {
|
||||
const newSelected = [...prev];
|
||||
newSelected.pop();
|
||||
return newSelected;
|
||||
})
|
||||
const handleKeyDown = React.useCallback(
|
||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
const input = inputRef.current;
|
||||
if (input) {
|
||||
if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||
if (input.value === '') {
|
||||
setSelected((prev) => {
|
||||
const newSelected = [...prev];
|
||||
newSelected.pop();
|
||||
return newSelected;
|
||||
});
|
||||
}
|
||||
}
|
||||
// This is not a default behaviour of the <input /> field
|
||||
if (e.key === 'Escape') {
|
||||
input.blur();
|
||||
}
|
||||
}
|
||||
// This is not a default behaviour of the <input /> field
|
||||
if (e.key === "Escape") {
|
||||
input.blur();
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const selectables = items.filter(
|
||||
(item) => !selected.find((s) => s.value === item.value)
|
||||
);
|
||||
|
||||
const selectables = items.filter(item => !selected.find(s => s.value === item.value));
|
||||
|
||||
return (
|
||||
<Command onKeyDown={handleKeyDown} className="overflow-visible bg-white">
|
||||
<div
|
||||
className="group border border-input px-3 py-2 text-sm ring-offset-background rounded-md focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2"
|
||||
>
|
||||
<div className="group border border-input px-3 py-2 text-sm ring-offset-background rounded-md focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2">
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
{selected.map((item) => {
|
||||
return (
|
||||
@@ -61,7 +65,7 @@ export function ComboboxMulti({ items, selected, setSelected, placeholder, ...pr
|
||||
<button
|
||||
className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
if (e.key === 'Enter') {
|
||||
handleUnselect(item);
|
||||
}
|
||||
}}
|
||||
@@ -74,7 +78,7 @@ export function ComboboxMulti({ items, selected, setSelected, placeholder, ...pr
|
||||
<X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
|
||||
</button>
|
||||
</Badge>
|
||||
)
|
||||
);
|
||||
})}
|
||||
{/* Avoid having the "Search" Icon */}
|
||||
<CommandPrimitive.Input
|
||||
@@ -89,7 +93,7 @@ export function ComboboxMulti({ items, selected, setSelected, placeholder, ...pr
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-2">
|
||||
{open && selectables.length > 0 ?
|
||||
{open && selectables.length > 0 ? (
|
||||
<div className="absolute w-full z-10 top-0 rounded-md border bg-popover text-popover-foreground shadow-md outline-none animate-in">
|
||||
<CommandGroup className="h-full overflow-auto">
|
||||
{selectables.map((item) => {
|
||||
@@ -101,10 +105,10 @@ export function ComboboxMulti({ items, selected, setSelected, placeholder, ...pr
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onSelect={(value) => {
|
||||
setInputValue("")
|
||||
setSelected(prev => [...prev, item])
|
||||
setInputValue('');
|
||||
setSelected((prev) => [...prev, item]);
|
||||
}}
|
||||
className={"cursor-pointer"}
|
||||
className={'cursor-pointer'}
|
||||
>
|
||||
{item.label}
|
||||
</CommandItem>
|
||||
@@ -112,8 +116,8 @@ export function ComboboxMulti({ items, selected, setSelected, placeholder, ...pr
|
||||
})}
|
||||
</CommandGroup>
|
||||
</div>
|
||||
: null}
|
||||
) : null}
|
||||
</div>
|
||||
</Command >
|
||||
)
|
||||
}
|
||||
</Command>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
import * as React from "react";
|
||||
import { Check, ChevronsUpDown } from "lucide-react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import * as React from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
} from '@/components/ui/command';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
} from '@/components/ui/popover';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { Check, ChevronsUpDown } from 'lucide-react';
|
||||
|
||||
type ComboboxProps = {
|
||||
interface ComboboxProps {
|
||||
placeholder: string;
|
||||
items: Array<{
|
||||
items: {
|
||||
value: string;
|
||||
label: string;
|
||||
}>;
|
||||
}[];
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
children?: React.ReactNode;
|
||||
onCreate?: (value: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export function Combobox({
|
||||
placeholder,
|
||||
@@ -37,10 +36,10 @@ export function Combobox({
|
||||
onCreate,
|
||||
}: ComboboxProps) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [search, setSearch] = React.useState("");
|
||||
const [search, setSearch] = React.useState('');
|
||||
function find(value: string) {
|
||||
return items.find(
|
||||
(item) => item.value.toLowerCase() === value.toLowerCase(),
|
||||
(item) => item.value.toLowerCase() === value.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +54,7 @@ export function Combobox({
|
||||
className="w-full min-w-0 justify-between"
|
||||
>
|
||||
<span className="overflow-hidden text-ellipsis">
|
||||
{value ? find(value)?.label ?? "No match" : placeholder}
|
||||
{value ? find(value)?.label ?? 'No match' : placeholder}
|
||||
</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
@@ -63,14 +62,22 @@ export function Combobox({
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full min-w-0 p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search item..." value={search} onValueChange={setSearch} />
|
||||
{typeof onCreate === "function" && search ? (
|
||||
<CommandInput
|
||||
placeholder="Search item..."
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
{typeof onCreate === 'function' && search ? (
|
||||
<CommandEmpty className="p-2">
|
||||
<Button onClick={() => {
|
||||
onCreate(search)
|
||||
setSearch('')
|
||||
setOpen(false)
|
||||
}}>Create "{search}"</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onCreate(search);
|
||||
setSearch('');
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
Create "{search}"
|
||||
</Button>
|
||||
</CommandEmpty>
|
||||
) : (
|
||||
<CommandEmpty>Nothing selected</CommandEmpty>
|
||||
@@ -88,8 +95,8 @@ export function Combobox({
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4 flex-shrink-0",
|
||||
value === item.value ? "opacity-100" : "opacity-0",
|
||||
'mr-2 h-4 w-4 flex-shrink-0',
|
||||
value === item.value ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
/>
|
||||
{item.label}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import * as React from "react"
|
||||
import { type DialogProps } from "@radix-ui/react-dialog"
|
||||
import { Command as CommandPrimitive } from "cmdk"
|
||||
import { Search } from "lucide-react"
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
||||
import * as React from 'react';
|
||||
import { Dialog, DialogContent } from '@/components/ui/dialog';
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { DialogProps } from '@radix-ui/react-dialog';
|
||||
import { Command as CommandPrimitive } from 'cmdk';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
const Command = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive>,
|
||||
@@ -13,15 +12,15 @@ const Command = React.forwardRef<
|
||||
<CommandPrimitive
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||
'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Command.displayName = CommandPrimitive.displayName
|
||||
));
|
||||
Command.displayName = CommandPrimitive.displayName;
|
||||
|
||||
type CommandDialogProps = DialogProps
|
||||
type CommandDialogProps = DialogProps;
|
||||
|
||||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||
return (
|
||||
@@ -32,8 +31,8 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const CommandInput = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||
@@ -44,15 +43,15 @@ const CommandInput = React.forwardRef<
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||
'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
));
|
||||
|
||||
CommandInput.displayName = CommandPrimitive.Input.displayName
|
||||
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
||||
|
||||
const CommandList = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.List>,
|
||||
@@ -60,12 +59,12 @@ const CommandList = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.List
|
||||
ref={ref}
|
||||
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||
className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
));
|
||||
|
||||
CommandList.displayName = CommandPrimitive.List.displayName
|
||||
CommandList.displayName = CommandPrimitive.List.displayName;
|
||||
|
||||
const CommandEmpty = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||
@@ -76,9 +75,9 @@ const CommandEmpty = React.forwardRef<
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
));
|
||||
|
||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
||||
|
||||
const CommandGroup = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||
@@ -87,14 +86,14 @@ const CommandGroup = React.forwardRef<
|
||||
<CommandPrimitive.Group
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
||||
'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
));
|
||||
|
||||
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
||||
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
||||
|
||||
const CommandSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||
@@ -102,11 +101,11 @@ const CommandSeparator = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 h-px bg-border", className)}
|
||||
className={cn('-mx-1 h-px bg-border', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
||||
));
|
||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
||||
|
||||
const CommandItem = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||
@@ -115,14 +114,14 @@ const CommandItem = React.forwardRef<
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
));
|
||||
|
||||
CommandItem.displayName = CommandPrimitive.Item.displayName
|
||||
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
||||
|
||||
const CommandShortcut = ({
|
||||
className,
|
||||
@@ -131,14 +130,14 @@ const CommandShortcut = ({
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||
'ml-auto text-xs tracking-widest text-muted-foreground',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
CommandShortcut.displayName = "CommandShortcut"
|
||||
);
|
||||
};
|
||||
CommandShortcut.displayName = 'CommandShortcut';
|
||||
|
||||
export {
|
||||
Command,
|
||||
@@ -150,4 +149,4 @@ export {
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandSeparator,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { X } from "lucide-react"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
||||
const Dialog = DialogPrimitive.Root
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal
|
||||
|
||||
const DialogClose = DialogPrimitive.Close
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
@@ -19,13 +18,13 @@ const DialogOverlay = React.forwardRef<
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
));
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
@@ -36,7 +35,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -48,8 +47,8 @@ const DialogContent = React.forwardRef<
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
));
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
@@ -57,13 +56,13 @@ const DialogHeader = ({
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
'flex flex-col space-y-1.5 text-center sm:text-left',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
);
|
||||
DialogHeader.displayName = 'DialogHeader';
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
@@ -71,13 +70,13 @@ const DialogFooter = ({
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogFooter.displayName = "DialogFooter"
|
||||
);
|
||||
DialogFooter.displayName = 'DialogFooter';
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
@@ -86,13 +85,13 @@ const DialogTitle = React.forwardRef<
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
'text-lg font-semibold leading-none tracking-tight',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
));
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
@@ -100,11 +99,11 @@ const DialogDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
className={cn('text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
));
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
@@ -117,4 +116,4 @@ export {
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||
import { Check, ChevronRight, Circle } from 'lucide-react';
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
||||
inset && "pl-8",
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
|
||||
inset && 'pl-8',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -34,9 +33,9 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
||||
{children}
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
));
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName
|
||||
DropdownMenuPrimitive.SubTrigger.displayName;
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
@@ -45,14 +44,14 @@ const DropdownMenuSubContent = React.forwardRef<
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
));
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
DropdownMenuPrimitive.SubContent.displayName;
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
@@ -63,32 +62,32 @@ const DropdownMenuContent = React.forwardRef<
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
));
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
inset && 'pl-8',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
));
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
@@ -97,7 +96,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
@@ -110,9 +109,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
));
|
||||
DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName;
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
@@ -121,7 +120,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -133,26 +132,26 @@ const DropdownMenuRadioItem = React.forwardRef<
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
))
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||
));
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
'px-2 py-1.5 text-sm font-semibold',
|
||||
inset && 'pl-8',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
));
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
@@ -160,11 +159,11 @@ const DropdownMenuSeparator = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
));
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
@@ -172,12 +171,12 @@ const DropdownMenuShortcut = ({
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||
className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
);
|
||||
};
|
||||
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
@@ -195,4 +194,4 @@ export {
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from "react"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
|
||||
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
|
||||
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
@@ -10,15 +9,15 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
);
|
||||
Input.displayName = 'Input';
|
||||
|
||||
export { Input }
|
||||
export { Input };
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import * as LabelPrimitive from '@radix-ui/react-label';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 block mb-2"
|
||||
)
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 block mb-2'
|
||||
);
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
@@ -18,7 +18,7 @@ const Label = React.forwardRef<
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
));
|
||||
Label.displayName = LabelPrimitive.Root.displayName;
|
||||
|
||||
export { Label }
|
||||
export { Label };
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
const Popover = PopoverPrimitive.Root;
|
||||
|
||||
const Popover = PopoverPrimitive.Root
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
));
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent }
|
||||
export { Popover, PopoverTrigger, PopoverContent };
|
||||
|
||||
@@ -1,34 +1,45 @@
|
||||
import * as React from "react"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
|
||||
export type RadioGroupProps = React.InputHTMLAttributes<HTMLDivElement>
|
||||
export type RadioGroupItemProps = React.InputHTMLAttributes<HTMLButtonElement> & {
|
||||
active?: boolean
|
||||
}
|
||||
export type RadioGroupProps = React.InputHTMLAttributes<HTMLDivElement>;
|
||||
export type RadioGroupItemProps =
|
||||
React.InputHTMLAttributes<HTMLButtonElement> & {
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-10 divide-x rounded-md border border-input bg-background text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
'flex h-10 divide-x rounded-md border border-input bg-background text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const RadioGroupItem = React.forwardRef<HTMLButtonElement, RadioGroupItemProps>(({className, active, ...props}, ref) => {
|
||||
return (
|
||||
<button {...props} className={cn('flex-1 px-3 whitespace-nowrap leading-none hover:bg-slate-100 transition-colors font-medium', className, active && 'bg-slate-100')} type="button" ref={ref} />
|
||||
)
|
||||
})
|
||||
const RadioGroupItem = React.forwardRef<HTMLButtonElement, RadioGroupItemProps>(
|
||||
({ className, active, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={cn(
|
||||
'flex-1 px-3 whitespace-nowrap leading-none hover:bg-slate-100 transition-colors font-medium',
|
||||
className,
|
||||
active && 'bg-slate-100'
|
||||
)}
|
||||
type="button"
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
RadioGroup.displayName = "RadioGroup"
|
||||
RadioGroupItem.displayName = "RadioGroupItem"
|
||||
RadioGroup.displayName = 'RadioGroup';
|
||||
RadioGroupItem.displayName = 'RadioGroupItem';
|
||||
|
||||
export { RadioGroup, RadioGroupItem }
|
||||
export { RadioGroup, RadioGroupItem };
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="border border-border rounded-md">
|
||||
<div className="relative w-full overflow-auto ">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm [&.mini]:text-xs", className)}
|
||||
{...props}
|
||||
/>
|
||||
<div className="relative w-full overflow-auto ">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'w-full caption-bottom text-sm [&.mini]:text-xs',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
))
|
||||
Table.displayName = "Table"
|
||||
));
|
||||
Table.displayName = 'Table';
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={className} {...props} />
|
||||
))
|
||||
TableHeader.displayName = "TableHeader"
|
||||
));
|
||||
TableHeader.displayName = 'TableHeader';
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
@@ -33,11 +34,11 @@ const TableBody = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
className={cn('[&_tr:last-child]:border-0', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableBody.displayName = "TableBody"
|
||||
));
|
||||
TableBody.displayName = 'TableBody';
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
@@ -45,11 +46,11 @@ const TableFooter = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn("bg-primary font-medium text-primary-foreground", className)}
|
||||
className={cn('bg-primary font-medium text-primary-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableFooter.displayName = "TableFooter"
|
||||
));
|
||||
TableFooter.displayName = 'TableFooter';
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
@@ -58,13 +59,13 @@ const TableRow = React.forwardRef<
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
'transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableRow.displayName = "TableRow"
|
||||
));
|
||||
TableRow.displayName = 'TableRow';
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
@@ -73,13 +74,13 @@ const TableHead = React.forwardRef<
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"p-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 shadow-[0_0_0_0.5px] shadow-border [.mini_&]:p-2",
|
||||
'p-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 shadow-[0_0_0_0.5px] shadow-border [.mini_&]:p-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableHead.displayName = "TableHead"
|
||||
));
|
||||
TableHead.displayName = 'TableHead';
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
@@ -87,11 +88,14 @@ const TableCell = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0 shadow-[0_0_0_0.5px] shadow-border [.mini_&]:p-2", className)}
|
||||
className={cn(
|
||||
'p-4 align-middle [&:has([role=checkbox])]:pr-0 shadow-[0_0_0_0.5px] shadow-border [.mini_&]:p-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCell.displayName = "TableCell"
|
||||
));
|
||||
TableCell.displayName = 'TableCell';
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
@@ -99,11 +103,11 @@ const TableCaption = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
className={cn('mt-4 text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCaption.displayName = "TableCaption"
|
||||
));
|
||||
TableCaption.displayName = 'TableCaption';
|
||||
|
||||
export {
|
||||
Table,
|
||||
@@ -114,4 +118,4 @@ export {
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as React from "react"
|
||||
import * as ToastPrimitives from "@radix-ui/react-toast"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { X } from "lucide-react"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import * as ToastPrimitives from '@radix-ui/react-toast';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
|
||||
const ToastProvider = ToastPrimitives.Provider
|
||||
const ToastProvider = ToastPrimitives.Provider;
|
||||
|
||||
const ToastViewport = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
||||
@@ -14,29 +14,29 @@ const ToastViewport = React.forwardRef<
|
||||
<ToastPrimitives.Viewport
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
||||
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
||||
));
|
||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
|
||||
|
||||
const toastVariants = cva(
|
||||
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
||||
'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "border bg-background text-foreground",
|
||||
default: 'border bg-background text-foreground',
|
||||
destructive:
|
||||
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
||||
'destructive group border-destructive bg-destructive text-destructive-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
variant: 'default',
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const Toast = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
@@ -49,9 +49,9 @@ const Toast = React.forwardRef<
|
||||
className={cn(toastVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
Toast.displayName = ToastPrimitives.Root.displayName
|
||||
);
|
||||
});
|
||||
Toast.displayName = ToastPrimitives.Root.displayName;
|
||||
|
||||
const ToastAction = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Action>,
|
||||
@@ -60,13 +60,13 @@ const ToastAction = React.forwardRef<
|
||||
<ToastPrimitives.Action
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
||||
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ToastAction.displayName = ToastPrimitives.Action.displayName
|
||||
));
|
||||
ToastAction.displayName = ToastPrimitives.Action.displayName;
|
||||
|
||||
const ToastClose = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Close>,
|
||||
@@ -75,7 +75,7 @@ const ToastClose = React.forwardRef<
|
||||
<ToastPrimitives.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
||||
'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
|
||||
className
|
||||
)}
|
||||
toast-close=""
|
||||
@@ -83,8 +83,8 @@ const ToastClose = React.forwardRef<
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</ToastPrimitives.Close>
|
||||
))
|
||||
ToastClose.displayName = ToastPrimitives.Close.displayName
|
||||
));
|
||||
ToastClose.displayName = ToastPrimitives.Close.displayName;
|
||||
|
||||
const ToastTitle = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Title>,
|
||||
@@ -92,11 +92,11 @@ const ToastTitle = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Title
|
||||
ref={ref}
|
||||
className={cn("text-sm font-semibold", className)}
|
||||
className={cn('text-sm font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
||||
));
|
||||
ToastTitle.displayName = ToastPrimitives.Title.displayName;
|
||||
|
||||
const ToastDescription = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Description>,
|
||||
@@ -104,15 +104,15 @@ const ToastDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm opacity-90", className)}
|
||||
className={cn('text-sm opacity-90', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ToastDescription.displayName = ToastPrimitives.Description.displayName
|
||||
));
|
||||
ToastDescription.displayName = ToastPrimitives.Description.displayName;
|
||||
|
||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
|
||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
|
||||
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction>
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction>;
|
||||
|
||||
export {
|
||||
type ToastProps,
|
||||
@@ -124,4 +124,4 @@ export {
|
||||
ToastDescription,
|
||||
ToastClose,
|
||||
ToastAction,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,11 +5,11 @@ import {
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
} from "@/components/ui/toast"
|
||||
import { useToast } from "@/components/ui/use-toast"
|
||||
} from '@/components/ui/toast';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
|
||||
export function Toaster() {
|
||||
const { toasts } = useToast()
|
||||
const { toasts } = useToast();
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
@@ -25,9 +25,9 @@ export function Toaster() {
|
||||
{action}
|
||||
<ToastClose />
|
||||
</Toast>
|
||||
)
|
||||
);
|
||||
})}
|
||||
<ToastViewport />
|
||||
</ToastProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||
|
||||
import { cn } from "@/utils/cn"
|
||||
const TooltipProvider = TooltipPrimitive.Provider;
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider
|
||||
const Tooltip = TooltipPrimitive.Root;
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
@@ -17,12 +16,12 @@ const TooltipContent = React.forwardRef<
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
|
||||
@@ -123,7 +123,7 @@ export const reducer = (state: State, action: Action): State => {
|
||||
}
|
||||
};
|
||||
|
||||
const listeners: Array<(state: State) => void> = [];
|
||||
const listeners: ((state: State) => void)[] = [];
|
||||
|
||||
let memoryState: State = { toasts: [] };
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { api, handleError } from "@/utils/api";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ContentHeader, ContentSection } from "@/components/Content";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { InputError } from "../forms/InputError";
|
||||
import { ContentHeader, ContentSection } from '@/components/Content';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { api, handleError } from '@/utils/api';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { InputError } from '../forms/InputError';
|
||||
|
||||
const validator = z
|
||||
.object({
|
||||
@@ -17,9 +18,9 @@ const validator = z
|
||||
.superRefine(({ confirmPassword, password }, ctx) => {
|
||||
if (confirmPassword !== password) {
|
||||
ctx.addIssue({
|
||||
path: ["confirmPassword"],
|
||||
code: "custom",
|
||||
message: "The passwords did not match",
|
||||
path: ['confirmPassword'],
|
||||
code: 'custom',
|
||||
message: 'The passwords did not match',
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -30,8 +31,8 @@ export function ChangePassword() {
|
||||
const mutation = api.user.changePassword.useMutation({
|
||||
onSuccess() {
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "You have updated your password",
|
||||
title: 'Success',
|
||||
description: 'You have updated your password',
|
||||
});
|
||||
},
|
||||
onError: handleError,
|
||||
@@ -40,16 +41,16 @@ export function ChangePassword() {
|
||||
const { register, handleSubmit, formState } = useForm<IForm>({
|
||||
resolver: zodResolver(validator),
|
||||
defaultValues: {
|
||||
oldPassword: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
oldPassword: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit((values) => {
|
||||
mutation.mutate(values)
|
||||
mutation.mutate(values);
|
||||
})}
|
||||
className="flex flex-col divide-y divide-border"
|
||||
>
|
||||
@@ -57,26 +58,37 @@ export function ChangePassword() {
|
||||
title="Change password"
|
||||
text="Need to change your password?"
|
||||
>
|
||||
<Button type="submit" disabled={!formState.isDirty}>Change it!</Button>
|
||||
<Button type="submit" disabled={!formState.isDirty}>
|
||||
Change it!
|
||||
</Button>
|
||||
</ContentHeader>
|
||||
<ContentSection title="Old password" text={<InputError {...formState.errors.oldPassword}/>}>
|
||||
<ContentSection
|
||||
title="Old password"
|
||||
text={<InputError {...formState.errors.oldPassword} />}
|
||||
>
|
||||
<Input
|
||||
type="password"
|
||||
{...register("oldPassword")}
|
||||
{...register('oldPassword')}
|
||||
placeholder="Old password"
|
||||
/>
|
||||
</ContentSection>
|
||||
<ContentSection title="New password" text={<InputError {...formState.errors.password}/>}>
|
||||
<ContentSection
|
||||
title="New password"
|
||||
text={<InputError {...formState.errors.password} />}
|
||||
>
|
||||
<Input
|
||||
type="password"
|
||||
{...register("password")}
|
||||
{...register('password')}
|
||||
placeholder="New password"
|
||||
/>
|
||||
</ContentSection>
|
||||
<ContentSection title="Confirm password" text={<InputError {...formState.errors.confirmPassword}/>}>
|
||||
<ContentSection
|
||||
title="Confirm password"
|
||||
text={<InputError {...formState.errors.confirmPassword} />}
|
||||
>
|
||||
<Input
|
||||
type="password"
|
||||
{...register("confirmPassword")}
|
||||
{...register('confirmPassword')}
|
||||
placeholder="Confirm password"
|
||||
/>
|
||||
</ContentSection>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type IInterval } from '@/types';
|
||||
import type { IInterval } from '@/types';
|
||||
|
||||
export function formatDateInterval(interval: IInterval, date: Date): string {
|
||||
if (interval === 'hour' || interval === 'minute') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { type z } from 'zod';
|
||||
import type { z } from 'zod';
|
||||
|
||||
export function useQueryParams<Z extends z.ZodTypeAny = z.ZodNever>(zod: Z) {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import { api, handleError } from "@/utils/api";
|
||||
import { ModalContent, ModalHeader } from "./Modal/Container";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ButtonContainer } from "@/components/ButtonContainer";
|
||||
import { popModal } from ".";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { InputWithLabel } from "@/components/forms/InputWithLabel";
|
||||
import { useRefetchActive } from "@/hooks/useRefetchActive";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Copy } from "lucide-react";
|
||||
import { clipboard } from "@/utils/clipboard";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { ButtonContainer } from '@/components/ButtonContainer';
|
||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { useRefetchActive } from '@/hooks/useRefetchActive';
|
||||
import { api, handleError } from '@/utils/api';
|
||||
import { clipboard } from '@/utils/clipboard';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Copy } from 'lucide-react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
const Syntax = dynamic(import('@/components/Syntax'))
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
const Syntax = dynamic(import('@/components/Syntax'));
|
||||
|
||||
const validator = z.object({
|
||||
name: z.string().min(1, "Required"),
|
||||
projectId: z.string().min(1, "Required"),
|
||||
name: z.string().min(1, 'Required'),
|
||||
projectId: z.string().min(1, 'Required'),
|
||||
});
|
||||
|
||||
type IForm = z.infer<typeof validator>;
|
||||
|
||||
export default function CreateProject() {
|
||||
const params = useOrganizationParams()
|
||||
const params = useOrganizationParams();
|
||||
const refetch = useRefetchActive();
|
||||
const query = api.project.list.useQuery({
|
||||
organizationSlug: params.organization,
|
||||
@@ -35,8 +36,8 @@ export default function CreateProject() {
|
||||
onError: handleError,
|
||||
onSuccess() {
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Client created!",
|
||||
title: 'Success',
|
||||
description: 'Client created!',
|
||||
});
|
||||
refetch();
|
||||
},
|
||||
@@ -44,8 +45,8 @@ export default function CreateProject() {
|
||||
const { register, handleSubmit, formState, control } = useForm<IForm>({
|
||||
resolver: zodResolver(validator),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
projectId: "",
|
||||
name: '',
|
||||
projectId: '',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -110,7 +111,7 @@ export default function CreateProject() {
|
||||
<InputWithLabel
|
||||
label="Name"
|
||||
placeholder="Name"
|
||||
{...register("name")}
|
||||
{...register('name')}
|
||||
className="mb-4"
|
||||
/>
|
||||
<Controller
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { api, handleError } from "@/utils/api";
|
||||
import { ModalContent, ModalHeader } from "./Modal/Container";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ButtonContainer } from "@/components/ButtonContainer";
|
||||
import { popModal } from ".";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { InputWithLabel } from "@/components/forms/InputWithLabel";
|
||||
import { useRefetchActive } from "@/hooks/useRefetchActive";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { ButtonContainer } from '@/components/ButtonContainer';
|
||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { useRefetchActive } from '@/hooks/useRefetchActive';
|
||||
import { api, handleError } from '@/utils/api';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
const validator = z.object({
|
||||
name: z.string().min(1),
|
||||
@@ -18,23 +19,23 @@ const validator = z.object({
|
||||
type IForm = z.infer<typeof validator>;
|
||||
|
||||
export default function AddProject() {
|
||||
const params = useOrganizationParams()
|
||||
const refetch = useRefetchActive()
|
||||
const params = useOrganizationParams();
|
||||
const refetch = useRefetchActive();
|
||||
const mutation = api.project.create.useMutation({
|
||||
onError: handleError,
|
||||
onSuccess() {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Project created! Lets create a client for it 🤘',
|
||||
})
|
||||
refetch()
|
||||
popModal()
|
||||
}
|
||||
});
|
||||
refetch();
|
||||
popModal();
|
||||
},
|
||||
});
|
||||
const { register, handleSubmit, formState } = useForm<IForm>({
|
||||
resolver: zodResolver(validator),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -51,8 +52,12 @@ export default function AddProject() {
|
||||
>
|
||||
<InputWithLabel label="Name" placeholder="Name" {...register('name')} />
|
||||
<ButtonContainer>
|
||||
<Button type="button" variant="outline" onClick={() => popModal()}>Cancel</Button>
|
||||
<Button type="submit" disabled={!formState.isDirty}>Create</Button>
|
||||
<Button type="button" variant="outline" onClick={() => popModal()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={!formState.isDirty}>
|
||||
Create
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</form>
|
||||
</ModalContent>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { ModalContent, ModalHeader } from "./Modal/Container";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ButtonContainer } from "@/components/ButtonContainer";
|
||||
import { popModal } from ".";
|
||||
import { ButtonContainer } from '@/components/ButtonContainer';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
export type ConfirmProps = {
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
export interface ConfirmProps {
|
||||
title: string;
|
||||
text: string;
|
||||
onConfirm: () => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export default function Confirm({
|
||||
title,
|
||||
@@ -24,7 +25,7 @@ export default function Confirm({
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
popModal("Confirm");
|
||||
popModal('Confirm');
|
||||
onCancel?.();
|
||||
}}
|
||||
>
|
||||
@@ -32,7 +33,7 @@ export default function Confirm({
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
popModal("Confirm");
|
||||
popModal('Confirm');
|
||||
onConfirm();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { api, handleError } from "@/utils/api";
|
||||
import { ModalContent, ModalHeader } from "./Modal/Container";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ButtonContainer } from "@/components/ButtonContainer";
|
||||
import { popModal } from ".";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { InputWithLabel } from "@/components/forms/InputWithLabel";
|
||||
import { useRefetchActive } from "@/hooks/useRefetchActive";
|
||||
import { useEffect } from 'react';
|
||||
import { ButtonContainer } from '@/components/ButtonContainer';
|
||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { useRefetchActive } from '@/hooks/useRefetchActive';
|
||||
import { api, handleError } from '@/utils/api';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
type EditClientProps = {
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
interface EditClientProps {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const validator = z.object({
|
||||
id: z.string().min(1),
|
||||
@@ -23,25 +24,25 @@ const validator = z.object({
|
||||
type IForm = z.infer<typeof validator>;
|
||||
|
||||
export default function EditClient({ id }: EditClientProps) {
|
||||
const refetch = useRefetchActive()
|
||||
const refetch = useRefetchActive();
|
||||
const mutation = api.client.update.useMutation({
|
||||
onError: handleError,
|
||||
onSuccess() {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Client updated.',
|
||||
})
|
||||
popModal()
|
||||
refetch()
|
||||
}
|
||||
});
|
||||
popModal();
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
const query = api.client.get.useQuery({ id });
|
||||
const data = query.data;
|
||||
const { register, handleSubmit, reset, formState } = useForm<IForm>({
|
||||
resolver: zodResolver(validator),
|
||||
defaultValues: {
|
||||
id: "",
|
||||
name: "",
|
||||
id: '',
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -61,8 +62,12 @@ export default function EditClient({ id }: EditClientProps) {
|
||||
>
|
||||
<InputWithLabel label="Name" placeholder="Name" {...register('name')} />
|
||||
<ButtonContainer>
|
||||
<Button type="button" variant="outline" onClick={() => popModal()}>Cancel</Button>
|
||||
<Button type="submit" disabled={!formState.isDirty}>Save</Button>
|
||||
<Button type="button" variant="outline" onClick={() => popModal()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={!formState.isDirty}>
|
||||
Save
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</form>
|
||||
</ModalContent>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { api, handleError } from "@/utils/api";
|
||||
import { ModalContent, ModalHeader } from "./Modal/Container";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ButtonContainer } from "@/components/ButtonContainer";
|
||||
import { popModal } from ".";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { InputWithLabel } from "@/components/forms/InputWithLabel";
|
||||
import { useRefetchActive } from "@/hooks/useRefetchActive";
|
||||
import { useEffect } from 'react';
|
||||
import { ButtonContainer } from '@/components/ButtonContainer';
|
||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { useRefetchActive } from '@/hooks/useRefetchActive';
|
||||
import { api, handleError } from '@/utils/api';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
type EditProjectProps = {
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
interface EditProjectProps {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const validator = z.object({
|
||||
id: z.string().min(1),
|
||||
@@ -23,25 +24,25 @@ const validator = z.object({
|
||||
type IForm = z.infer<typeof validator>;
|
||||
|
||||
export default function EditProject({ id }: EditProjectProps) {
|
||||
const refetch = useRefetchActive()
|
||||
const refetch = useRefetchActive();
|
||||
const mutation = api.project.update.useMutation({
|
||||
onError: handleError,
|
||||
onSuccess() {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Project updated.',
|
||||
})
|
||||
popModal()
|
||||
refetch()
|
||||
}
|
||||
});
|
||||
popModal();
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
const query = api.project.get.useQuery({ id });
|
||||
const data = query.data;
|
||||
const { register, handleSubmit, reset,formState } = useForm<IForm>({
|
||||
const { register, handleSubmit, reset, formState } = useForm<IForm>({
|
||||
resolver: zodResolver(validator),
|
||||
defaultValues: {
|
||||
id: "",
|
||||
name: "",
|
||||
id: '',
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -61,8 +62,12 @@ export default function EditProject({ id }: EditProjectProps) {
|
||||
>
|
||||
<InputWithLabel label="Name" placeholder="Name" {...register('name')} />
|
||||
<ButtonContainer>
|
||||
<Button type="button" variant="outline" onClick={() => popModal()}>Cancel</Button>
|
||||
<Button type="submit" disabled={!formState.isDirty}>Save</Button>
|
||||
<Button type="button" variant="outline" onClick={() => popModal()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={!formState.isDirty}>
|
||||
Save
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</form>
|
||||
</ModalContent>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { X } from "lucide-react";
|
||||
import { popModal } from "..";
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
type ModalContentProps = {
|
||||
import { popModal } from '..';
|
||||
|
||||
interface ModalContentProps {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
}
|
||||
|
||||
export function ModalContent({ children }: ModalContentProps) {
|
||||
return (
|
||||
@@ -14,9 +15,9 @@ export function ModalContent({ children }: ModalContentProps) {
|
||||
);
|
||||
}
|
||||
|
||||
type ModalHeaderProps = {
|
||||
interface ModalHeaderProps {
|
||||
title: string | React.ReactNode;
|
||||
};
|
||||
}
|
||||
|
||||
export function ModalHeader({ title }: ModalHeaderProps) {
|
||||
return (
|
||||
|
||||
@@ -1,47 +1,48 @@
|
||||
import { api, handleError } from "@/utils/api";
|
||||
import { ModalContent, ModalHeader } from "./Modal/Container";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ButtonContainer } from "@/components/ButtonContainer";
|
||||
import { popModal } from ".";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { InputWithLabel } from "@/components/forms/InputWithLabel";
|
||||
import { useRefetchActive } from "@/hooks/useRefetchActive";
|
||||
import { type IChartInput } from "@/types";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { useRouter } from "next/router";
|
||||
import { ButtonContainer } from '@/components/ButtonContainer';
|
||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { useRefetchActive } from '@/hooks/useRefetchActive';
|
||||
import type { IChartInput } from '@/types';
|
||||
import { api, handleError } from '@/utils/api';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Controller, useForm, useWatch } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
type SaveReportProps = {
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
interface SaveReportProps {
|
||||
report: IChartInput;
|
||||
reportId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const validator = z.object({
|
||||
name: z.string().min(1, "Required"),
|
||||
projectId: z.string().min(1, "Required"),
|
||||
dashboardId: z.string().min(1, "Required"),
|
||||
name: z.string().min(1, 'Required'),
|
||||
projectId: z.string().min(1, 'Required'),
|
||||
dashboardId: z.string().min(1, 'Required'),
|
||||
});
|
||||
|
||||
type IForm = z.infer<typeof validator>;
|
||||
|
||||
export default function SaveReport({ report }: SaveReportProps) {
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const { organization } = useOrganizationParams();
|
||||
const refetch = useRefetchActive();
|
||||
const save = api.report.save.useMutation({
|
||||
onError: handleError,
|
||||
onSuccess(res) {
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Report saved.",
|
||||
title: 'Success',
|
||||
description: 'Report saved.',
|
||||
});
|
||||
popModal();
|
||||
refetch();
|
||||
router.push(`/${organization}/reports/${res.id}`)
|
||||
router.push(`/${organization}/reports/${res.id}`);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -49,26 +50,26 @@ export default function SaveReport({ report }: SaveReportProps) {
|
||||
useForm<IForm>({
|
||||
resolver: zodResolver(validator),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
projectId: "",
|
||||
dashboardId: "",
|
||||
name: '',
|
||||
projectId: '',
|
||||
dashboardId: '',
|
||||
},
|
||||
});
|
||||
|
||||
const dashboardMutation = api.dashboard.create.useMutation({
|
||||
onError: handleError,
|
||||
onSuccess(res) {
|
||||
setValue("dashboardId", res.id);
|
||||
setValue('dashboardId', res.id);
|
||||
dashboasrdQuery.refetch();
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Dashboard created.",
|
||||
title: 'Success',
|
||||
description: 'Dashboard created.',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const projectId = useWatch({
|
||||
name: "projectId",
|
||||
name: 'projectId',
|
||||
control,
|
||||
});
|
||||
|
||||
@@ -82,7 +83,7 @@ export default function SaveReport({ report }: SaveReportProps) {
|
||||
},
|
||||
{
|
||||
enabled: !!projectId,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const projects = (projectQuery.data ?? []).map((item) => ({
|
||||
@@ -113,7 +114,7 @@ export default function SaveReport({ report }: SaveReportProps) {
|
||||
<InputWithLabel
|
||||
label="Report name"
|
||||
placeholder="Name"
|
||||
{...register("name")}
|
||||
{...register('name')}
|
||||
defaultValue={report.name}
|
||||
/>
|
||||
<Controller
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Suspense, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { Loader } from 'lucide-react';
|
||||
import mitt from 'mitt';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import { type ConfirmProps } from './Confirm';
|
||||
|
||||
import type { ConfirmProps } from './Confirm';
|
||||
|
||||
const Loading = () => (
|
||||
<div className='fixed top-0 z-50 flex h-screen w-screen items-center justify-center overflow-auto bg-backdrop'>
|
||||
<Loader className='mb-8 animate-spin' size={40} />
|
||||
<div className="fixed top-0 z-50 flex h-screen w-screen items-center justify-center overflow-auto bg-backdrop">
|
||||
<Loader className="mb-8 animate-spin" size={40} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -48,26 +48,29 @@ const emitter = mitt<{
|
||||
|
||||
type ModalRoutes = keyof typeof modals;
|
||||
|
||||
type StateItem = {
|
||||
interface StateItem {
|
||||
key: string;
|
||||
name: ModalRoutes;
|
||||
props: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
type ModalWrapperProps = {
|
||||
interface ModalWrapperProps {
|
||||
children: React.ReactNode;
|
||||
name: ModalRoutes;
|
||||
isOnTop: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
function ModalWrapper({ children, name, isOnTop }: ModalWrapperProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useOnClickOutside(ref, (event) => {
|
||||
const target = event.target as HTMLElement;
|
||||
const isPortal = typeof target.closest === 'function' ? !!target.closest('[data-radix-popper-content-wrapper]') : false
|
||||
|
||||
if (isOnTop && !isPortal) {
|
||||
const isPortal =
|
||||
typeof target.closest === 'function'
|
||||
? !!target.closest('[data-radix-popper-content-wrapper]')
|
||||
: false;
|
||||
|
||||
if (isOnTop && !isPortal) {
|
||||
emitter.emit('pop', {
|
||||
name,
|
||||
});
|
||||
@@ -75,8 +78,8 @@ function ModalWrapper({ children, name, isOnTop }: ModalWrapperProps) {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='fixed top-0 z-50 flex h-screen w-screen items-center justify-center overflow-auto'>
|
||||
<div ref={ref} className='w-inherit m-auto py-4'>
|
||||
<div className="fixed top-0 z-50 flex h-screen w-screen items-center justify-center overflow-auto">
|
||||
<div ref={ref} className="w-inherit m-auto py-4">
|
||||
<Suspense>{children}</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,7 +144,9 @@ export function ModalProvider() {
|
||||
});
|
||||
return (
|
||||
<>
|
||||
{!!state.length && <div className="fixed top-0 left-0 right-0 bottom-0 bg-[rgba(0,0,0,0.2)]"></div>}
|
||||
{!!state.length && (
|
||||
<div className="fixed top-0 left-0 right-0 bottom-0 bg-[rgba(0,0,0,0.2)]"></div>
|
||||
)}
|
||||
{state.map((item, index) => {
|
||||
const Modal = modals[item.name];
|
||||
return (
|
||||
@@ -168,7 +173,7 @@ type OrUndefined<T> = T extends Record<string, never> ? undefined : T;
|
||||
|
||||
export const pushModal = <
|
||||
T extends StateItem['name'],
|
||||
B extends OrUndefined<GetComponentProps<(typeof modals)[T]>>
|
||||
B extends OrUndefined<GetComponentProps<(typeof modals)[T]>>,
|
||||
>(
|
||||
name: T,
|
||||
...rest: B extends undefined ? [] : [B]
|
||||
@@ -179,7 +184,7 @@ export const pushModal = <
|
||||
});
|
||||
export const replaceModal = <
|
||||
T extends StateItem['name'],
|
||||
B extends OrUndefined<GetComponentProps<(typeof modals)[T]>>
|
||||
B extends OrUndefined<GetComponentProps<(typeof modals)[T]>>,
|
||||
>(
|
||||
name: T,
|
||||
...rest: B extends undefined ? [] : [B]
|
||||
@@ -197,4 +202,4 @@ export const unshiftModal = (name: StateItem['name']) =>
|
||||
name,
|
||||
});
|
||||
|
||||
export const showConfirm = (props: ConfirmProps) => pushModal('Confirm', props);
|
||||
export const showConfirm = (props: ConfirmProps) => pushModal('Confirm', props);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { MainLayout } from "@/components/layouts/MainLayout";
|
||||
import { Container } from "@/components/Container";
|
||||
import { api } from "@/utils/api";
|
||||
import Link from "next/link";
|
||||
import { PageTitle } from "@/components/PageTitle";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { Suspense, useMemo, useState } from "react";
|
||||
import { createServerSideProps } from "@/server/getServerSideProps";
|
||||
import { Chart } from "@/components/report/chart";
|
||||
import { timeRanges } from "@/utils/constants";
|
||||
import { type IChartRange } from "@/types";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { getRangeLabel } from "@/utils/getRangeLabel";
|
||||
import { Suspense, useMemo, useState } from 'react';
|
||||
import { Container } from '@/components/Container';
|
||||
import { MainLayout } from '@/components/layouts/MainLayout';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Chart } from '@/components/report/chart';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { createServerSideProps } from '@/server/getServerSideProps';
|
||||
import type { IChartRange } from '@/types';
|
||||
import { api } from '@/utils/api';
|
||||
import { timeRanges } from '@/utils/constants';
|
||||
import { getRangeLabel } from '@/utils/getRangeLabel';
|
||||
import Link from 'next/link';
|
||||
|
||||
export const getServerSideProps = createServerSideProps();
|
||||
|
||||
@@ -66,7 +66,9 @@ export default function Dashboard() {
|
||||
<div className="font-medium">{report.name}</div>
|
||||
{chartRange && (
|
||||
<div className="mt-2 text-sm flex gap-2">
|
||||
<span className={range ? "line-through" : ""}>{chartRange}</span>
|
||||
<span className={range ? 'line-through' : ''}>
|
||||
{chartRange}
|
||||
</span>
|
||||
{range && <span>{getRangeLabel(range)}</span>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Container } from "@/components/Container";
|
||||
import { PageTitle } from "@/components/PageTitle";
|
||||
import { usePagination } from "@/components/Pagination";
|
||||
import { EventsTable } from "@/components/events/EventsTable";
|
||||
import { MainLayout } from "@/components/layouts/MainLayout";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useMemo } from 'react';
|
||||
import { Container } from '@/components/Container';
|
||||
import { EventsTable } from '@/components/events/EventsTable';
|
||||
import { MainLayout } from '@/components/layouts/MainLayout';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { usePagination } from '@/components/Pagination';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { api } from '@/utils/api';
|
||||
|
||||
export default function Events() {
|
||||
const pagination = usePagination();
|
||||
@@ -18,7 +17,7 @@ export default function Events() {
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
const events = useMemo(() => eventsQuery.data ?? [], [eventsQuery]);
|
||||
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
import { MainLayout } from "@/components/layouts/MainLayout";
|
||||
import { Container } from "@/components/Container";
|
||||
import { api } from "@/utils/api";
|
||||
import Link from "next/link";
|
||||
import { PageTitle } from "@/components/PageTitle";
|
||||
import { Card } from "@/components/Card";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { createServerSideProps } from "@/server/getServerSideProps";
|
||||
import { Card } from '@/components/Card';
|
||||
import { Container } from '@/components/Container';
|
||||
import { MainLayout } from '@/components/layouts/MainLayout';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { createServerSideProps } from '@/server/getServerSideProps';
|
||||
import { api } from '@/utils/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
export const getServerSideProps = createServerSideProps()
|
||||
export const getServerSideProps = createServerSideProps();
|
||||
|
||||
export default function Home() {
|
||||
const params = useOrganizationParams();
|
||||
const query = api.dashboard.list.useQuery({
|
||||
projectSlug: params.project,
|
||||
}, {
|
||||
enabled: Boolean(params.organization && params.project),
|
||||
});
|
||||
const query = api.dashboard.list.useQuery(
|
||||
{
|
||||
projectSlug: params.project,
|
||||
},
|
||||
{
|
||||
enabled: Boolean(params.organization && params.project),
|
||||
}
|
||||
);
|
||||
const dashboards = query.data ?? [];
|
||||
|
||||
return (
|
||||
@@ -25,13 +28,13 @@ export default function Home() {
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{dashboards.map((item) => (
|
||||
<Card key={item.id}>
|
||||
<Link
|
||||
href={`/${params.organization}/${params.project}/${item.slug}`}
|
||||
className="block p-4 font-medium leading-none hover:underline"
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
</Card>
|
||||
<Link
|
||||
href={`/${params.organization}/${params.project}/${item.slug}`}
|
||||
className="block p-4 font-medium leading-none hover:underline"
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Container } from "@/components/Container";
|
||||
import { PageTitle } from "@/components/PageTitle";
|
||||
import { usePagination } from "@/components/Pagination";
|
||||
import { EventsTable } from "@/components/events/EventsTable";
|
||||
import { MainLayout } from "@/components/layouts/MainLayout";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { useQueryParams } from "@/hooks/useQueryParams";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { z } from "zod";
|
||||
import { useMemo } from 'react';
|
||||
import { Container } from '@/components/Container';
|
||||
import { EventsTable } from '@/components/events/EventsTable';
|
||||
import { MainLayout } from '@/components/layouts/MainLayout';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { usePagination } from '@/components/Pagination';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { useQueryParams } from '@/hooks/useQueryParams';
|
||||
import { api } from '@/utils/api';
|
||||
import { z } from 'zod';
|
||||
|
||||
export default function ProfileId() {
|
||||
const pagination = usePagination();
|
||||
@@ -16,7 +15,7 @@ export default function ProfileId() {
|
||||
const { profileId } = useQueryParams(
|
||||
z.object({
|
||||
profileId: z.string(),
|
||||
}),
|
||||
})
|
||||
);
|
||||
const eventsQuery = api.event.list.useQuery(
|
||||
{
|
||||
@@ -26,7 +25,7 @@ export default function ProfileId() {
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
const events = useMemo(() => eventsQuery.data ?? [], [eventsQuery]);
|
||||
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import { Container } from "@/components/Container";
|
||||
import { DataTable } from "@/components/DataTable";
|
||||
import { PageTitle } from "@/components/PageTitle";
|
||||
import { Pagination, usePagination } from "@/components/Pagination";
|
||||
import { MainLayout } from "@/components/layouts/MainLayout";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { type RouterOutputs, api } from "@/utils/api";
|
||||
import { formatDateTime } from "@/utils/date";
|
||||
import { toDots } from "@/utils/object";
|
||||
import { AvatarImage } from "@radix-ui/react-avatar";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import Link from "next/link";
|
||||
import { useMemo } from "react";
|
||||
import { useMemo } from 'react';
|
||||
import { Container } from '@/components/Container';
|
||||
import { DataTable } from '@/components/DataTable';
|
||||
import { MainLayout } from '@/components/layouts/MainLayout';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Pagination, usePagination } from '@/components/Pagination';
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import { Table, TableBody, TableCell, TableRow } from '@/components/ui/table';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { api } from '@/utils/api';
|
||||
import type { RouterOutputs } from '@/utils/api';
|
||||
import { formatDateTime } from '@/utils/date';
|
||||
import { toDots } from '@/utils/object';
|
||||
import { AvatarImage } from '@radix-ui/react-avatar';
|
||||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import Link from 'next/link';
|
||||
|
||||
const columnHelper =
|
||||
createColumnHelper<RouterOutputs["profile"]["list"][number]>();
|
||||
createColumnHelper<RouterOutputs['profile']['list'][number]>();
|
||||
|
||||
export default function Events() {
|
||||
const pagination = usePagination();
|
||||
@@ -27,42 +28,45 @@ export default function Events() {
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
const profiles = useMemo(() => eventsQuery.data ?? [], [eventsQuery]);
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
columnHelper.accessor((row) => row.createdAt, {
|
||||
id: "createdAt",
|
||||
header: () => "Created At",
|
||||
id: 'createdAt',
|
||||
header: () => 'Created At',
|
||||
cell(info) {
|
||||
return formatDateTime(info.getValue());
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('first_name', {
|
||||
id: "name",
|
||||
header: () => "Name",
|
||||
id: 'name',
|
||||
header: () => 'Name',
|
||||
cell(info) {
|
||||
const profile = info.row.original;
|
||||
return (
|
||||
<Link href={`/${params.organization}/${params.project}/profiles/${profile?.id}`} className="flex items-center gap-2">
|
||||
<Link
|
||||
href={`/${params.organization}/${params.project}/profiles/${profile?.id}`}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Avatar className="h-6 w-6">
|
||||
{profile?.avatar && <AvatarImage src={profile.avatar} />}
|
||||
<AvatarFallback className="text-xs">
|
||||
{profile?.first_name?.at(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{`${profile?.first_name} ${profile?.last_name ?? ""}`}
|
||||
{`${profile?.first_name} ${profile?.last_name ?? ''}`}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor((row) => row.properties, {
|
||||
id: "properties",
|
||||
header: () => "Properties",
|
||||
id: 'properties',
|
||||
header: () => 'Properties',
|
||||
cell(info) {
|
||||
const dots = toDots(info.getValue() as Record<string, any>);
|
||||
if(Object.keys(dots).length === 0) return 'No properties';
|
||||
if (Object.keys(dots).length === 0) return 'No properties';
|
||||
return (
|
||||
<Table className="mini">
|
||||
<TableBody>
|
||||
@@ -71,10 +75,10 @@ export default function Events() {
|
||||
<TableRow key={key}>
|
||||
<TableCell className="font-medium">{key}</TableCell>
|
||||
<TableCell>
|
||||
{typeof dots[key] === "boolean"
|
||||
{typeof dots[key] === 'boolean'
|
||||
? dots[key]
|
||||
? "true"
|
||||
: "false"
|
||||
? 'true'
|
||||
: 'false'
|
||||
: dots[key]}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import { MainLayout } from "@/components/layouts/MainLayout";
|
||||
import { Container } from "@/components/Container";
|
||||
import { api } from "@/utils/api";
|
||||
import Link from "next/link";
|
||||
import { PageTitle } from "@/components/PageTitle";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { Card } from "@/components/Card";
|
||||
import { createServerSideProps } from "@/server/getServerSideProps";
|
||||
import { Card } from '@/components/Card';
|
||||
import { Container } from '@/components/Container';
|
||||
import { MainLayout } from '@/components/layouts/MainLayout';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { createServerSideProps } from '@/server/getServerSideProps';
|
||||
import { api } from '@/utils/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
export const getServerSideProps = createServerSideProps()
|
||||
export const getServerSideProps = createServerSideProps();
|
||||
|
||||
export default function Home() {
|
||||
const params = useOrganizationParams(
|
||||
const params = useOrganizationParams();
|
||||
|
||||
const query = api.project.list.useQuery(
|
||||
{
|
||||
organizationSlug: params.organization,
|
||||
},
|
||||
{
|
||||
enabled: !!params.organization,
|
||||
}
|
||||
);
|
||||
|
||||
const query = api.project.list.useQuery({
|
||||
organizationSlug: params.organization,
|
||||
}, {
|
||||
enabled: !!params.organization,
|
||||
});
|
||||
|
||||
const projects = query.data ?? [];
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { default } from "./index";
|
||||
import { createServerSideProps } from '@/server/getServerSideProps';
|
||||
|
||||
import { createServerSideProps } from "@/server/getServerSideProps";
|
||||
export { default } from './index';
|
||||
|
||||
export const getServerSideProps = createServerSideProps()
|
||||
export const getServerSideProps = createServerSideProps();
|
||||
|
||||
@@ -1,37 +1,42 @@
|
||||
import { ReportSidebar } from "@/components/report/sidebar/ReportSidebar";
|
||||
import { Chart } from "@/components/report/chart";
|
||||
import { useDispatch, useSelector } from "@/redux";
|
||||
import { MainLayout } from "@/components/layouts/MainLayout";
|
||||
import { ReportDateRange } from "@/components/report/ReportDateRange";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { reset, setReport } from "@/components/report/reportSlice";
|
||||
import { useReportId } from "@/components/report/hooks/useReportId";
|
||||
import { api } from "@/utils/api";
|
||||
import { useRouterBeforeLeave } from "@/hooks/useRouterBeforeLeave";
|
||||
import { createServerSideProps } from "@/server/getServerSideProps";
|
||||
import { ReportChartType } from "@/components/report/ReportChartType";
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { MainLayout } from '@/components/layouts/MainLayout';
|
||||
import { Chart } from '@/components/report/chart';
|
||||
import { useReportId } from '@/components/report/hooks/useReportId';
|
||||
import { ReportChartType } from '@/components/report/ReportChartType';
|
||||
import { ReportDateRange } from '@/components/report/ReportDateRange';
|
||||
import { reset, setReport } from '@/components/report/reportSlice';
|
||||
import { ReportSidebar } from '@/components/report/sidebar/ReportSidebar';
|
||||
import { useRouterBeforeLeave } from '@/hooks/useRouterBeforeLeave';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import { createServerSideProps } from '@/server/getServerSideProps';
|
||||
import { api } from '@/utils/api';
|
||||
|
||||
export const getServerSideProps = createServerSideProps()
|
||||
export const getServerSideProps = createServerSideProps();
|
||||
|
||||
export default function Page() {
|
||||
const { reportId } = useReportId();
|
||||
const dispatch = useDispatch();
|
||||
const report = useSelector((state) => state.report);
|
||||
const reportQuery = api.report.get.useQuery({ id: String(reportId) }, {
|
||||
enabled: Boolean(reportId),
|
||||
})
|
||||
const reportQuery = api.report.get.useQuery(
|
||||
{ id: String(reportId) },
|
||||
{
|
||||
enabled: Boolean(reportId),
|
||||
}
|
||||
);
|
||||
|
||||
// Reset report state before leaving
|
||||
useRouterBeforeLeave(useCallback(() => {
|
||||
dispatch(reset())
|
||||
}, [dispatch]))
|
||||
useRouterBeforeLeave(
|
||||
useCallback(() => {
|
||||
dispatch(reset());
|
||||
}, [dispatch])
|
||||
);
|
||||
|
||||
// Set report if reportId exists
|
||||
useEffect(() => {
|
||||
if(reportId && reportQuery.data) {
|
||||
dispatch(setReport(reportQuery.data))
|
||||
if (reportId && reportQuery.data) {
|
||||
dispatch(setReport(reportQuery.data));
|
||||
}
|
||||
}, [reportId, reportQuery.data, dispatch])
|
||||
}, [reportId, reportQuery.data, dispatch]);
|
||||
|
||||
return (
|
||||
<MainLayout className="grid min-h-screen grid-cols-[400px_minmax(0,1fr)] divide-x">
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { api } from "@/utils/api";
|
||||
import { ContentHeader } from "@/components/Content";
|
||||
import { SettingsLayout } from "@/components/layouts/SettingsLayout";
|
||||
import { DataTable } from "@/components/DataTable";
|
||||
import { columns } from "@/components/clients/table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { pushModal } from "@/modals";
|
||||
import { createServerSideProps } from "@/server/getServerSideProps";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { columns } from '@/components/clients/table';
|
||||
import { ContentHeader } from '@/components/Content';
|
||||
import { DataTable } from '@/components/DataTable';
|
||||
import { SettingsLayout } from '@/components/layouts/SettingsLayout';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { pushModal } from '@/modals';
|
||||
import { createServerSideProps } from '@/server/getServerSideProps';
|
||||
import { api } from '@/utils/api';
|
||||
|
||||
export const getServerSideProps = createServerSideProps()
|
||||
export const getServerSideProps = createServerSideProps();
|
||||
|
||||
export default function Clients() {
|
||||
const params = useOrganizationParams();
|
||||
@@ -19,7 +19,7 @@ export default function Clients() {
|
||||
return (
|
||||
<SettingsLayout>
|
||||
<ContentHeader title="Clients" text="List of your clients">
|
||||
<Button onClick={() => pushModal("AddClient")}>Create</Button>
|
||||
<Button onClick={() => pushModal('AddClient')}>Create</Button>
|
||||
</ContentHeader>
|
||||
<DataTable data={data} columns={columns} />
|
||||
</SettingsLayout>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { api, handleError } from "@/utils/api";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ContentHeader, ContentSection } from "@/components/Content";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useEffect } from "react";
|
||||
import { SettingsLayout } from "@/components/layouts/SettingsLayout";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { createServerSideProps } from "@/server/getServerSideProps";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { z } from "zod";
|
||||
import { InputError } from "@/components/forms/InputError";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from 'react';
|
||||
import { ContentHeader, ContentSection } from '@/components/Content';
|
||||
import { InputError } from '@/components/forms/InputError';
|
||||
import { SettingsLayout } from '@/components/layouts/SettingsLayout';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { createServerSideProps } from '@/server/getServerSideProps';
|
||||
import { api, handleError } from '@/utils/api';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const getServerSideProps = createServerSideProps();
|
||||
|
||||
@@ -22,7 +22,7 @@ const validator = z.object({
|
||||
type IForm = z.infer<typeof validator>;
|
||||
|
||||
export default function Organization() {
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const params = useOrganizationParams();
|
||||
const query = api.organization.get.useQuery({
|
||||
slug: params.organization,
|
||||
@@ -30,11 +30,11 @@ export default function Organization() {
|
||||
const mutation = api.organization.update.useMutation({
|
||||
onSuccess(res) {
|
||||
toast({
|
||||
title: "Organization updated",
|
||||
description: "Your organization has been updated.",
|
||||
title: 'Organization updated',
|
||||
description: 'Your organization has been updated.',
|
||||
});
|
||||
query.refetch();
|
||||
router.replace(`/${res.slug}/settings/organization`)
|
||||
router.replace(`/${res.slug}/settings/organization`);
|
||||
},
|
||||
onError: handleError,
|
||||
});
|
||||
@@ -42,8 +42,8 @@ export default function Organization() {
|
||||
|
||||
const { register, handleSubmit, reset, formState } = useForm<IForm>({
|
||||
defaultValues: {
|
||||
id: "",
|
||||
name: "",
|
||||
id: '',
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -53,12 +53,11 @@ export default function Organization() {
|
||||
}
|
||||
}, [data, reset]);
|
||||
|
||||
|
||||
return (
|
||||
<SettingsLayout>
|
||||
<form
|
||||
onSubmit={handleSubmit((values) => {
|
||||
mutation.mutate(values)
|
||||
mutation.mutate(values);
|
||||
})}
|
||||
className="flex flex-col divide-y divide-border"
|
||||
>
|
||||
@@ -66,10 +65,18 @@ export default function Organization() {
|
||||
title="Organization"
|
||||
text="View and update your organization"
|
||||
>
|
||||
<Button type="submit" disabled={!formState.isDirty}>Save</Button>
|
||||
<Button type="submit" disabled={!formState.isDirty}>
|
||||
Save
|
||||
</Button>
|
||||
</ContentHeader>
|
||||
<ContentSection title="Name" text={["Notice. Changing name will result in a url change as well.", <InputError key="error" {...formState.errors.name} />]}>
|
||||
<Input {...register("name")} />
|
||||
<ContentSection
|
||||
title="Name"
|
||||
text={[
|
||||
'Notice. Changing name will result in a url change as well.',
|
||||
<InputError key="error" {...formState.errors.name} />,
|
||||
]}
|
||||
>
|
||||
<Input {...register('name')} />
|
||||
</ContentSection>
|
||||
<ContentSection
|
||||
title="Invite user"
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { api, handleError } from "@/utils/api";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ContentHeader, ContentSection } from "@/components/Content";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useEffect } from "react";
|
||||
import { SettingsLayout } from "@/components/layouts/SettingsLayout";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { createServerSideProps } from "@/server/getServerSideProps";
|
||||
import { ChangePassword } from "@/components/user/ChangePassword";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { InputError } from "@/components/forms/InputError";
|
||||
import { useEffect } from 'react';
|
||||
import { ContentHeader, ContentSection } from '@/components/Content';
|
||||
import { InputError } from '@/components/forms/InputError';
|
||||
import { SettingsLayout } from '@/components/layouts/SettingsLayout';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { ChangePassword } from '@/components/user/ChangePassword';
|
||||
import { createServerSideProps } from '@/server/getServerSideProps';
|
||||
import { api, handleError } from '@/utils/api';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const getServerSideProps = createServerSideProps();
|
||||
|
||||
const validator = z.object({
|
||||
name: z.string().min(2),
|
||||
email: z.string().email(),
|
||||
})
|
||||
});
|
||||
|
||||
type IForm = z.infer<typeof validator>;
|
||||
|
||||
@@ -26,8 +26,8 @@ export default function Profile() {
|
||||
const mutation = api.user.update.useMutation({
|
||||
onSuccess() {
|
||||
toast({
|
||||
title: "Profile updated",
|
||||
description: "Your profile has been updated.",
|
||||
title: 'Profile updated',
|
||||
description: 'Your profile has been updated.',
|
||||
});
|
||||
query.refetch();
|
||||
},
|
||||
@@ -38,8 +38,8 @@ export default function Profile() {
|
||||
const { register, handleSubmit, reset, formState } = useForm<IForm>({
|
||||
resolver: zodResolver(validator),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
email: "",
|
||||
name: '',
|
||||
email: '',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -60,14 +60,23 @@ export default function Profile() {
|
||||
Save
|
||||
</Button>
|
||||
</ContentHeader>
|
||||
<ContentSection title="Name" text={[
|
||||
"Your full name",
|
||||
<InputError key="error" {...formState.errors.name} />
|
||||
]}>
|
||||
<Input {...register("name")} />
|
||||
<ContentSection
|
||||
title="Name"
|
||||
text={[
|
||||
'Your full name',
|
||||
<InputError key="error" {...formState.errors.name} />,
|
||||
]}
|
||||
>
|
||||
<Input {...register('name')} />
|
||||
</ContentSection>
|
||||
<ContentSection title="Mail" text={["Your email address", <InputError key="error" {...formState.errors.email} />]}>
|
||||
<Input {...register("email")} />
|
||||
<ContentSection
|
||||
title="Mail"
|
||||
text={[
|
||||
'Your email address',
|
||||
<InputError key="error" {...formState.errors.email} />,
|
||||
]}
|
||||
>
|
||||
<Input {...register('email')} />
|
||||
</ContentSection>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { api } from "@/utils/api";
|
||||
import { ContentHeader } from "@/components/Content";
|
||||
import { SettingsLayout } from "@/components/layouts/SettingsLayout";
|
||||
import { DataTable } from "@/components/DataTable";
|
||||
import { columns } from "@/components/projects/table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { pushModal } from "@/modals";
|
||||
import { useOrganizationParams } from "@/hooks/useOrganizationParams";
|
||||
import { createServerSideProps } from "@/server/getServerSideProps";
|
||||
import { ContentHeader } from '@/components/Content';
|
||||
import { DataTable } from '@/components/DataTable';
|
||||
import { SettingsLayout } from '@/components/layouts/SettingsLayout';
|
||||
import { columns } from '@/components/projects/table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
import { pushModal } from '@/modals';
|
||||
import { createServerSideProps } from '@/server/getServerSideProps';
|
||||
import { api } from '@/utils/api';
|
||||
|
||||
export const getServerSideProps = createServerSideProps()
|
||||
export const getServerSideProps = createServerSideProps();
|
||||
|
||||
export default function Projects() {
|
||||
const params = useOrganizationParams()
|
||||
const params = useOrganizationParams();
|
||||
const query = api.project.list.useQuery({
|
||||
organizationSlug: params.organization
|
||||
organizationSlug: params.organization,
|
||||
});
|
||||
const data = query.data ?? [];
|
||||
return (
|
||||
<SettingsLayout>
|
||||
<ContentHeader title="Projects" text="List of your projects">
|
||||
<Button onClick={() => pushModal('AddProject')}>Create</Button>
|
||||
</ContentHeader>
|
||||
</ContentHeader>
|
||||
<DataTable data={data} columns={columns} />
|
||||
</SettingsLayout>
|
||||
);
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Suspense } from 'react';
|
||||
import { Toaster } from '@/components/ui/toaster';
|
||||
import store from '@/redux';
|
||||
import { api } from '@/utils/api';
|
||||
import { type Session } from 'next-auth';
|
||||
import type { Session } from 'next-auth';
|
||||
import { SessionProvider } from 'next-auth/react';
|
||||
import { type AppType } from 'next/app';
|
||||
import type { AppType } from 'next/app';
|
||||
import { Space_Grotesk } from 'next/font/google';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ import { db } from '@/server/db';
|
||||
import { createError, handleError } from '@/server/exceptions';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
import { type EventPayload } from '@mixan/types';
|
||||
import type { EventPayload } from '@mixan/types';
|
||||
|
||||
interface Request extends NextApiRequest {
|
||||
body: Array<EventPayload>;
|
||||
body: EventPayload[];
|
||||
}
|
||||
|
||||
export default async function handler(req: Request, res: NextApiResponse) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createError, handleError } from '@/server/exceptions';
|
||||
import { tickProfileProperty } from '@/server/services/profile.service';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
import { type ProfileIncrementPayload } from '@mixan/types';
|
||||
import type { ProfileIncrementPayload } from '@mixan/types';
|
||||
|
||||
interface Request extends NextApiRequest {
|
||||
body: ProfileIncrementPayload;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createError, handleError } from '@/server/exceptions';
|
||||
import { tickProfileProperty } from '@/server/services/profile.service';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
import { type ProfileIncrementPayload } from '@mixan/types';
|
||||
import type { ProfileIncrementPayload } from '@mixan/types';
|
||||
|
||||
interface Request extends NextApiRequest {
|
||||
body: ProfileIncrementPayload;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createError, handleError } from '@/server/exceptions';
|
||||
import { getProfile } from '@/server/services/profile.service';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
import { type ProfilePayload } from '@mixan/types';
|
||||
import type { ProfilePayload } from '@mixan/types';
|
||||
|
||||
interface Request extends NextApiRequest {
|
||||
body: ProfilePayload;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { randomUUID } from 'crypto';
|
||||
import { db } from '@/server/db';
|
||||
import { handleError } from '@/server/exceptions';
|
||||
import { hashPassword } from '@/server/services/hash.service';
|
||||
import { type NextApiRequest, type NextApiResponse } from 'next';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
|
||||
@@ -3,8 +3,8 @@ import { configureStore } from '@reduxjs/toolkit';
|
||||
import {
|
||||
useDispatch as useBaseDispatch,
|
||||
useSelector as useBaseSelector,
|
||||
type TypedUseSelectorHook,
|
||||
} from 'react-redux';
|
||||
import type { TypedUseSelectorHook } from 'react-redux';
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
||||
import { db } from '@/server/db';
|
||||
import {
|
||||
type IChartEvent,
|
||||
type IChartInputWithDates,
|
||||
type IChartRange,
|
||||
} from '@/types';
|
||||
import type { IChartEvent, IChartInputWithDates, IChartRange } from '@/types';
|
||||
import { getDaysOldDate } from '@/utils/date';
|
||||
import { toDots } from '@/utils/object';
|
||||
import { zChartInputWithDates } from '@/utils/validation';
|
||||
@@ -157,11 +153,11 @@ function isJsonPath(property: string) {
|
||||
return property.startsWith('properties');
|
||||
}
|
||||
|
||||
type ResultItem = {
|
||||
interface ResultItem {
|
||||
label: string | null;
|
||||
count: number;
|
||||
date: string;
|
||||
};
|
||||
}
|
||||
|
||||
function propertyNameToSql(name: string) {
|
||||
if (name.includes('.')) {
|
||||
|
||||
@@ -2,16 +2,16 @@ import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
||||
import { db } from '@/server/db';
|
||||
import { getDashboardBySlug } from '@/server/services/dashboard.service';
|
||||
import { getProjectBySlug } from '@/server/services/project.service';
|
||||
import {
|
||||
type IChartBreakdown,
|
||||
type IChartEvent,
|
||||
type IChartEventFilter,
|
||||
type IChartInput,
|
||||
type IChartRange,
|
||||
import type {
|
||||
IChartBreakdown,
|
||||
IChartEvent,
|
||||
IChartEventFilter,
|
||||
IChartInput,
|
||||
IChartRange,
|
||||
} from '@/types';
|
||||
import { alphabetIds } from '@/utils/constants';
|
||||
import { zChartInput } from '@/utils/validation';
|
||||
import { type Report as DbReport } from '@prisma/client';
|
||||
import type { Report as DbReport } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
function transformFilter(
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
import { getServerAuthSession } from '@/server/auth';
|
||||
import { db } from '@/server/db';
|
||||
import { initTRPC, TRPCError } from '@trpc/server';
|
||||
import { type CreateNextContextOptions } from '@trpc/server/adapters/next';
|
||||
import { type Session } from 'next-auth';
|
||||
import type { CreateNextContextOptions } from '@trpc/server/adapters/next';
|
||||
import type { Session } from 'next-auth';
|
||||
import superjson from 'superjson';
|
||||
import { ZodError } from 'zod';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user