1 Commits

162 changed files with 23978 additions and 4431 deletions
+3 -2
View File
@@ -23,13 +23,14 @@
"vscode": {
"extensions": [
"oven.bun-vscode",
"dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss",
"YoavBls.pretty-ts-errors",
"usernamehw.errorlens",
"Prisma.prisma",
"esbenp.prettier-vscode",
"dsznajder.es7-react-js-snippets",
"csstools.postcss",
"biomejs.biome"
"csstools.postcss"
]
}
},
+1 -8
View File
@@ -13,11 +13,4 @@ NODE_ENV=production
DEMO_MODE=false
DOMAIN=localhost
AUTH_TRUST_HOST="http://localhost"
AUTH_SECRET=your_secret_key_here
# ADMIN BOOTSTRAP
ADMIN_BOOTSTRAP_ENABLED=true
ADMIN_USERNAME=admin
ADMIN_EMAIL=admin@localhost
ADMIN_NAME=Administrator
ADMIN_PASSWORD=change-me
AUTH_SECRET=your_secret_key_here
-10
View File
@@ -17,9 +17,6 @@
/.next/
/out/
# prisma
src/generated
# production
/build
@@ -42,10 +39,3 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# vscode
!.vscode
# Local Pi runtime state
.atl/
.pi
+15
View File
@@ -0,0 +1,15 @@
node_modules
.next
.husky
coverage
.prettierignore
.stylelintignore
.eslintignore
stories
storybook-static
*.log
playwright-report
.nyc_output
test-results
junit.xml
docs
+11
View File
@@ -0,0 +1,11 @@
{
"useTabs": false,
"trailingComma": "all",
"semi": false,
"tabWidth": 2,
"singleQuote": false,
"printWidth": 80,
"endOfLine": "auto",
"arrowParens": "always",
"plugins": ["prettier-plugin-tailwindcss"]
}
+18 -13
View File
@@ -1,15 +1,20 @@
{
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"source.fixAll.biome": "explicit"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
},
"eslint.useFlatConfig": true,
"eslint.format.enable": true,
"eslint.run": "onSave",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
}
+1 -1
View File
@@ -39,4 +39,4 @@ COPY --from=builder /app/.next/static ./.next/static
EXPOSE ${PORT}
CMD ["sh", "-c", "bun run db:deploy && bun run db:seed && bun run start"]
CMD ["bun", "run", "start"]
-66
View File
@@ -1,66 +0,0 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "main"
},
"files": {
"includes": [
"**",
"!**/ node_modules",
"!**/ .next",
"!src/generated/prisma",
"!src/components/ui",
"!src/styles"
],
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 80,
"attributePosition": "auto",
"bracketSameLine": false,
"bracketSpacing": true,
"expand": "auto",
"useEditorconfig": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "asNeeded",
"arrowParentheses": "always",
"bracketSameLine": false,
"quoteStyle": "double",
"attributePosition": "auto",
"bracketSpacing": true
}
},
"html": {
"formatter": {
"indentScriptAndStyle": false,
"selfCloseVoidElements": "always"
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}
+671 -400
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -5,7 +5,7 @@
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/globals.css",
"css": "src/app/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
@@ -18,4 +18,4 @@
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
}
-5
View File
@@ -37,11 +37,6 @@ services:
DOMAIN: ${DOMAIN}
AUTH_TRUST_HOST: ${AUTH_TRUST_HOST}
AUTH_SECRET: ${AUTH_SECRET}
ADMIN_BOOTSTRAP_ENABLED: ${ADMIN_BOOTSTRAP_ENABLED:-"true"}
ADMIN_USERNAME: ${ADMIN_USERNAME:-"admin"}
ADMIN_EMAIL: ${ADMIN_EMAIL:-"admin@localhost"}
ADMIN_NAME: ${ADMIN_NAME:-"Administrator"}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public
depends_on:
- db
+59
View File
@@ -0,0 +1,59 @@
import { FlatCompat } from "@eslint/eslintrc"
import eslintPlugin from "@eslint/js"
import type { Linter } from "eslint"
const compat = new FlatCompat()
const eslintConfig = [
{
name: "custom/eslint/recommended",
files: ["**/*.ts?(x)"],
...eslintPlugin.configs.recommended,
},
]
const ignoresConfig = [
{
name: "custom/eslint/ignores",
// the ignores option needs to be in a separate configuration object
// replaces the .eslintignore file
ignores: [
".next/",
".vscode/",
"public/",
"src/generated/",
"node_modules/",
"src/components/ui/",
],
},
] as Linter.Config[]
export default [
...compat.extends(
"next/core-web-vitals",
"next/typescript",
"plugin:import/recommended",
"plugin:playwright/recommended",
"plugin:prettier/recommended",
),
...compat.config({
rules: {
"no-unused-vars": "error",
"simple-import-sort/exports": "error",
"simple-import-sort/imports": "error",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-object-type": "off",
"@typescript-eslint/no-empty-interface": "off",
},
plugins: ["simple-import-sort"],
globals: { React: true, Prisma: true },
settings: {
react: {
version: "detect",
},
},
}),
...eslintConfig,
...ignoresConfig,
] satisfies Linter.Config[]
+5
View File
@@ -3,6 +3,11 @@ import type { NextConfig } from "next"
const nextConfig: NextConfig = {
/* config options here */
eslint: {
// we have added a lint command to the package.json build script
// which is why we disable the default next lint (during builds) here
ignoreDuringBuilds: true,
},
}
export default nextConfig
+31 -21
View File
@@ -2,28 +2,29 @@
"name": "stock-manager",
"version": "0.1.0",
"private": true,
"packageManager": "bun@1.3.14",
"type": "module",
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "biome lint --write",
"format": "biome format --write",
"check": "biome check --write",
"next-lint": "next lint",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"db:push": "bunx prisma db push",
"db:migrate": "bunx prisma migrate dev",
"db:migrate:reset": "bunx prisma migrate reset",
"db:deploy": "bunx prisma migrate deploy",
"db:generate": "bunx prisma generate",
"db:seed": "bunx --bun prisma db seed",
"db:studio": "bunx prisma studio"
},
"prisma": {
"schema": "src/prisma/schema.prisma",
"seed": "bun src/prisma/seed.ts"
},
"dependencies": {
"@base-ui/react": "^1.4.1",
"@hookform/resolvers": "^5.2.2",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"@eslint/js": "^9.29.0",
"@hookform/resolvers": "^5.1.1",
"@prisma/client": "^6.10.1",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dialog": "^1.1.14",
@@ -35,28 +36,37 @@
"bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dotenv": "^17.4.2",
"lucide-react": "^1.17.0",
"next": "^16.2.4",
"lucide-react": "^0.518.0",
"next": "15.3.6",
"next-auth": "^5.0.0-beta.28",
"next-themes": "^0.4.6",
"papaparse": "^5.5.3",
"radix-ui": "^1.4.3",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-hook-form": "^7.74.0",
"sonner": "^2.0.7",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.58.1",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.1",
"use-debounce": "^10.0.6",
"zod": "^4.3.6"
"zod": "^3.25.67"
},
"devDependencies": {
"@biomejs/biome": "2.4.15",
"@eslint/eslintrc": "^3.3.1",
"@tailwindcss/postcss": "^4.1.10",
"@types/node": "^24.0.3",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"prisma": "^7.8.0",
"@typescript-eslint/parser": "^8.34.1",
"eslint": "^9.29.0",
"eslint-config-next": "15.3.4",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-playwright": "^2.2.0",
"eslint-plugin-prettier": "^5.5.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unicorn": "^59.0.1",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.13",
"prisma": "^6.10.1",
"tailwindcss": "^4.1.10",
"tw-animate-css": "^1.3.4",
"typescript": "^5.8.3"
-13
View File
@@ -1,13 +0,0 @@
import "dotenv/config"
import { defineConfig, env } from "prisma/config"
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
seed: "bun ./prisma/seed.ts",
},
datasource: {
url: env("DATABASE_URL"),
},
})
-80
View File
@@ -1,80 +0,0 @@
import { fileURLToPath } from "node:url"
import { getPasswordHash } from "@/lib/security"
import prisma from "../src/lib/prisma"
type BootstrapAdminInput = {
username: string
email: string
name: string
password: string
}
function getBootstrapAdminInput(): BootstrapAdminInput {
const isProduction = process.env.NODE_ENV === "production"
const username = process.env.ADMIN_USERNAME ?? "admin"
const email = process.env.ADMIN_EMAIL ?? "admin@localhost"
const name = process.env.ADMIN_NAME ?? "Administrator"
const password = process.env.ADMIN_PASSWORD
if (isProduction && !password) {
throw new Error("ADMIN_PASSWORD is required to bootstrap an admin user")
}
return {
username,
email,
name,
password: password ?? "admin",
}
}
export async function bootstrapAdmin(client: typeof prisma) {
const enabled = process.env.ADMIN_BOOTSTRAP_ENABLED !== "false"
const existingAdmin = await client.user.findFirst({
where: {
role: "ADMIN",
isActive: true,
},
select: {
id: true,
},
})
if (existingAdmin || !enabled) return
const admin = getBootstrapAdminInput()
await client.user.upsert({
where: {
email: admin.email,
},
update: {
role: "ADMIN",
isActive: true,
},
create: {
name: admin.name,
username: admin.username,
email: admin.email,
role: "ADMIN",
password: await getPasswordHash(admin.password),
isActive: true,
},
})
}
async function main() {
try {
await bootstrapAdmin(prisma)
} finally {
await prisma.$disconnect()
}
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main().catch((error) => {
console.error(error)
process.exit(1)
})
}
@@ -1,2 +0,0 @@
-- CreateIndex
CREATE UNIQUE INDEX "Item_name_key" ON "Item"("name");
-184
View File
@@ -1,184 +0,0 @@
// This is your Prisma schema file,
// learn more about the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
binaryTargets = ["native", "debian-openssl-1.1.x"]
}
datasource db {
provider = "postgresql"
}
enum UserRole {
ADMIN
MANAGER
STAFF
VIEWER
}
model User {
id String @id @default(uuid())
username String @unique
name String
email String @unique
password String
role UserRole @default(STAFF)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
movements Movement[]
assignments Assignment[]
}
enum RecipientDepartment {
IT
ENGINEERING
LOGISTICS
TRAFFIC
DRIVER
ADMINISTRATION
SALES
OTHER
}
model Recipient {
id String @id @default(uuid())
username String @unique
firstName String
lastName String
department RecipientDepartment?
email String? @unique
phone String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
assignments Assignment[]
movements Movement[]
@@index([lastName, firstName])
@@index([department])
}
model Category {
id String @id @default(uuid())
name String @unique
description String?
isActive Boolean @default(true)
items Item[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([name])
}
enum ItemStatus {
AVAILABLE
ASSIGNED
RESERVED
IN_REPAIR
BROKEN
STOLEN
DISPOSED
}
model Item {
id String @id @default(uuid())
name String @unique
description String?
categoryId String
category Category @relation(fields: [categoryId], references: [id])
stock Int @default(0)
minStock Int?
maxStock Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
movements Movement[]
assignments Assignment[]
assets Asset[]
@@index([categoryId])
}
model Asset {
id String @id @default(uuid())
itemId String?
item Item? @relation(fields: [itemId], references: [id])
serialNumber String @unique
deliveryNote String?
status ItemStatus @default(AVAILABLE)
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
movements Movement[]
assignment Assignment?
@@index([serialNumber])
@@index([itemId])
@@index([status])
}
model Assignment {
id String @id @default(uuid())
quantity Int?
notes String?
itemId String?
item Item? @relation(fields: [itemId], references: [id])
assetId String? @unique
asset Asset? @relation(fields: [assetId], references: [id])
recipientId String?
recipient Recipient? @relation(fields: [recipientId], references: [id], onDelete: Cascade, onUpdate: Cascade)
assignmentDate DateTime @default(now())
returnDate DateTime?
createdBy String
createdUser User @relation(fields: [createdBy], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
movement Movement[]
@@index([itemId])
@@index([assetId])
@@index([recipientId])
@@index([createdBy])
}
enum MovementType {
IN
OUT
ASSIGNMENT
RETURN
ADJUSTMENT
DELETED
}
model Movement {
id String @id @default(uuid())
type MovementType @default(IN)
quantity Int
details String?
notes String?
itemId String?
item Item? @relation(fields: [itemId], references: [id])
assetId String?
asset Asset? @relation(fields: [assetId], references: [id])
previousStock Int?
newStock Int?
recipientId String?
recipient Recipient? @relation(fields: [recipientId], references: [id])
assignmentId String?
assignment Assignment? @relation(fields: [assignmentId], references: [id])
userId String
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
@@index([itemId])
@@index([assetId])
@@index([recipientId])
@@index([type])
@@index([userId])
}
-17
View File
@@ -1,17 +0,0 @@
import prisma from "../src/lib/prisma"
import { bootstrapAdmin } from "./bootstrap-admin"
async function main() {
await bootstrapAdmin(prisma)
}
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})
-93
View File
@@ -1,93 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import { flattenError } from "zod"
import {
type CreateAssetFormType,
createAssetSchema,
type UpdateAssetFormType,
updateAssetSchema,
} from "@/schemas/asset.schema"
import { getAuthenticatedUserId } from "@/services/auth.service"
import {
createAssetUseCase,
updateAssetUseCase,
} from "@/use-cases/asset.use-cases"
export async function createAssetAction(formData: CreateAssetFormType) {
try {
const validatedFields = createAssetSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: flattenError(validatedFields.error).fieldErrors,
}
}
const userId = await getAuthenticatedUserId()
const result = await createAssetUseCase({
...validatedFields.data,
actorId: userId,
})
if (!result.success) {
return result
}
revalidatePath("/inventory/assets")
revalidatePath("/inventory/items")
revalidatePath("/assignments")
revalidatePath("/movements")
return {
success: true,
message: "Asset created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
message: "Error creating asset",
}
}
}
export async function updateAssetAction(formData: UpdateAssetFormType) {
const validatedFields = updateAssetSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: flattenError(validatedFields.error).fieldErrors,
}
}
try {
const userId = await getAuthenticatedUserId()
const result = await updateAssetUseCase({
...validatedFields.data,
actorId: userId,
})
if (!result.success) {
return result
}
revalidatePath("/inventory/assets")
revalidatePath("/inventory/items")
revalidatePath("/assignments")
revalidatePath("/movements")
return {
success: true,
message: "Asset updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
message: "Error updating asset",
}
}
}
-118
View File
@@ -1,118 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import { getAuthenticatedUserId } from "@/services/auth.service"
import {
createAssignmentUseCase,
returnAssignmentUseCase,
updateAssignmentUseCase,
} from "@/use-cases/assignment.use-cases"
import {
assignmentSchema,
type CreateAssignmentFormType,
type ReturnAssignmentFormType,
type UpdateAssignmentFormType,
updateAssignmentSchema,
} from "@/schemas/assignment.schema"
export async function createAssignment(formData: CreateAssignmentFormType) {
const createdBy = await getAuthenticatedUserId()
const validatedFields = assignmentSchema.safeParse({
...formData,
createdBy,
})
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await createAssignmentUseCase({
...validatedFields.data,
actorId: createdBy,
})
if (!result.success) {
return result
}
revalidatePath("/assignments")
return {
success: true,
message: "Assignment created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
errors: { error: ["Error creating assignment"] },
}
}
}
export async function updateAssignment(formData: UpdateAssignmentFormType) {
const validatedFields = updateAssignmentSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const createdBy = await getAuthenticatedUserId()
const result = await updateAssignmentUseCase({
...validatedFields.data,
actorId: createdBy,
})
if (!result.success) {
return result
}
revalidatePath("/assignments")
return {
success: true,
message: "Assignment updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false,
errors: { error: ["Error updating assignment"] },
}
}
}
export async function returnAssignment(formData: ReturnAssignmentFormType) {
const { id } = formData
const userId = await getAuthenticatedUserId()
const result = await returnAssignmentUseCase({
id,
actorId: userId,
})
if (!result.success) {
return {
...result,
message: "Error returning assignment",
}
}
revalidatePath("/assignments")
return {
success: true as const,
message: "Assignment returned successfully",
}
}
-116
View File
@@ -1,116 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import {
type CreateItemFormType,
createItemSchema,
type UpdateItemFormType,
updateItemSchema,
} from "@/schemas/item.schema"
import { getAuthenticatedUserId } from "@/services/auth.service"
import {
createItemUseCase,
deleteItemUseCase,
updateItemUseCase,
} from "@/use-cases/item.use-cases"
export async function createItemAction(formData: CreateItemFormType) {
const validatedFields = createItemSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const userId = await getAuthenticatedUserId()
const result = await createItemUseCase({
...validatedFields.data,
actorId: userId,
})
if (!result.success) {
return result
}
revalidatePath("/inventory/items")
revalidatePath("/movements")
return {
success: true,
message: "Item created successfully!",
}
} catch (error) {
console.error("Database error:", error)
return {
error: "Error creating item",
}
}
}
export async function updateItemAction(formData: UpdateItemFormType) {
const validatedFields = updateItemSchema.safeParse(formData)
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const userId = await getAuthenticatedUserId()
const result = await updateItemUseCase({
...validatedFields.data,
actorId: userId,
})
if (!result.success) {
return result
}
revalidatePath("/inventory/items")
revalidatePath("/movements")
return {
success: true,
message: "Item updated successfully!",
}
} catch (error) {
console.error("Database error:", error)
return {
error: "Failed to update item",
}
}
}
export async function deleteItemAction(formData: FormData) {
const { id } = Object.fromEntries(formData) as { id: string }
try {
const result = await deleteItemUseCase(id)
if (!result.success) {
return {
...result,
message: "Failed to delete item",
}
}
revalidatePath("/inventory/items")
return {
success: true as const,
message: "Item deleted successfully!",
}
} catch (error) {
console.error("Database error:", error)
return {
success: false as const,
message: "Failed to delete item",
errors: {},
}
}
}
-76
View File
@@ -1,76 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import {
type CreateRecipientFormType,
createRecipientSchema,
type UpdateRecipientFormType,
updateRecipientSchema,
} from "@/schemas/recipient.schema"
import {
createRecipientUseCase,
updateRecipientUseCase,
} from "@/use-cases/recipient.use-cases"
export async function createNewRecipient(formData: CreateRecipientFormType) {
const validatedFields = createRecipientSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await createRecipientUseCase(validatedFields.data)
if (!result.success) {
return result
}
revalidatePath("/recipients")
return {
success: true,
message: "Recipient created successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
message: "Failed to create recipient",
}
}
}
export async function updateRecipient(formData: UpdateRecipientFormType) {
const validatedFields = updateRecipientSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await updateRecipientUseCase(validatedFields.data)
if (!result.success) {
return result
}
revalidatePath("/recipients")
return {
success: true,
message: "Recipient updated successfully",
}
} catch (error) {
console.error("Database error:", error)
return {
message: "Failed to update recipient",
}
}
}
-144
View File
@@ -1,144 +0,0 @@
"use server"
import { revalidatePath } from "next/cache"
import { flattenError } from "zod"
import {
type CreateUserFormType,
createUserSchema,
type ResetUserPasswordFormType,
resetUserPasswordSchema,
type SetUserActiveFormType,
setUserActiveSchema,
type UpdateUserFormType,
updateUserSchema,
} from "@/schemas/user.schema"
import { requireRole } from "@/services/auth.service"
import {
createUserUseCase,
resetUserPasswordUseCase,
setUserActiveUseCase,
updateUserUseCase,
} from "@/use-cases/user.use-cases"
const USERS_PATH = "/admin/users"
export async function createUserAction(formData: CreateUserFormType) {
await requireRole("ADMIN")
const validatedFields = createUserSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await createUserUseCase(validatedFields.data)
if (!result.success) {
return result
}
revalidatePath(USERS_PATH)
return { success: true, message: "User created successfully" }
} catch (error) {
console.error("Database error:", error)
return { success: false, message: "Failed to create user" }
}
}
export async function updateUserAction(formData: UpdateUserFormType) {
const session = await requireRole("ADMIN")
const validatedFields = updateUserSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: flattenError(validatedFields.error).fieldErrors,
}
}
try {
const result = await updateUserUseCase({
...validatedFields.data,
actorId: session.user.id,
})
if (!result.success) {
return result
}
revalidatePath(USERS_PATH)
return { success: true, message: "User updated successfully" }
} catch (error) {
console.error("Database error:", error)
return { success: false, message: "Failed to update user" }
}
}
export async function setUserActiveAction(formData: SetUserActiveFormType) {
const session = await requireRole("ADMIN")
const validatedFields = setUserActiveSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: flattenError(validatedFields.error).fieldErrors,
}
}
try {
const result = await setUserActiveUseCase({
...validatedFields.data,
actorId: session.user.id,
})
if (!result.success) {
return result
}
revalidatePath(USERS_PATH)
return { success: true, message: "User status updated successfully" }
} catch (error) {
console.error("Database error:", error)
return { success: false, message: "Failed to update user status" }
}
}
export async function resetUserPasswordAction(
formData: ResetUserPasswordFormType,
) {
await requireRole("ADMIN")
const validatedFields = resetUserPasswordSchema.safeParse(formData)
if (!validatedFields.success) {
return {
success: false,
errors: validatedFields.error.flatten().fieldErrors,
}
}
try {
const result = await resetUserPasswordUseCase(validatedFields.data)
if (!result.success) {
return result
}
revalidatePath(USERS_PATH)
return { success: true, message: "Password reset successfully" }
} catch (error) {
console.error("Database error:", error)
return { success: false, message: "Failed to reset password" }
}
}
@@ -4,9 +4,10 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter, useSearchParams } from "next/navigation"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { signInAction } from "@/actions/auth.actions"
import { Button } from "@/components/ui/button"
import { type SignInFormType, signInSchema } from "@/schemas/auth.schema"
import { signInAction } from "@/lib/actions/auth.actions"
import { SignInFormType, signInSchema } from "@/lib/schemas/auth.schemas"
export default function SignInForm() {
const router = useRouter()
-6
View File
@@ -24,8 +24,6 @@ export default async function Home() {
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
role="img"
aria-label="total-items"
>
<path
strokeLinecap="round"
@@ -47,8 +45,6 @@ export default async function Home() {
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
role="img"
aria-label="total-assets"
>
<path
strokeLinecap="round"
@@ -70,8 +66,6 @@ export default async function Home() {
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
role="img"
aria-label="total-recipients"
>
<path
strokeLinecap="round"
-13
View File
@@ -1,13 +0,0 @@
import type { ReactNode } from "react"
import { requireRole } from "@/services/auth.service"
export default async function AdminLayout({
children,
}: {
children: ReactNode
}) {
await requireRole("ADMIN")
return children
}
@@ -1,32 +0,0 @@
import { notFound } from "next/navigation"
import { getUserProfileById } from "@/services/user.service"
import EditUserForm from "../../_components/edit.user.form"
import ResetUserPasswordForm from "../../_components/reset.user.password.form"
export default async function EditUserPage({
params,
}: {
params: Promise<{ userId: string }>
}) {
const { userId } = await params
const user = await getUserProfileById(userId)
if (!user) {
notFound()
}
return (
<div className="flex flex-col gap-8">
<div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">Edit User</h1>
</div>
<EditUserForm user={user} />
<section className="flex flex-col gap-4 border-t pt-6">
<h2 className="text-xl font-semibold">Reset password</h2>
<ResetUserPasswordForm userId={user.id} />
</section>
</div>
)
}
@@ -1,141 +0,0 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import type { UseFormRegisterReturn } from "react-hook-form"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateUserAction } from "@/actions/user.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import {
type UpdateUserFormType,
updateUserSchema,
} from "@/schemas/user.schema"
import type { UserWithoutPassword } from "@/services/user.service"
export default function EditUserForm({ user }: { user: UserWithoutPassword }) {
const router = useRouter()
const {
register,
handleSubmit,
setError,
formState: { errors, isSubmitting, isSubmitSuccessful },
} = useForm<UpdateUserFormType>({
resolver: zodResolver(updateUserSchema),
defaultValues: {
id: user.id,
name: user.name,
username: user.username,
email: user.email,
role: user.role,
isActive: user.isActive,
},
})
const onSubmit = async (formData: UpdateUserFormType) => {
const response = await updateUserAction(formData)
if (response?.errors) {
Object.entries(response.errors).forEach(([fieldName, messages]) => {
messages.forEach((message: string) => {
setError(fieldName as keyof UpdateUserFormType, {
type: "server",
message,
})
toast.error(message)
})
})
return
}
if (response?.success) {
toast.success(response.message)
router.push("/admin/users")
}
}
return (
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<input type="hidden" {...register("id")} />
<UserTextInput
error={errors.name?.message}
id="name"
label="Name"
placeholder="Full name"
register={register("name")}
/>
<UserTextInput
error={errors.username?.message}
id="username"
label="Username"
placeholder="username"
register={register("username")}
/>
<UserTextInput
error={errors.email?.message}
id="email"
label="Email"
placeholder="user@example.com"
register={register("email")}
type="email"
/>
<div className="flex flex-col gap-2">
<label htmlFor="role" className="mb-2 block text-lg">
Role
</label>
<select
id="role"
{...register("role")}
className="w-full rounded-lg border px-4 py-2"
>
<option value="ADMIN">Admin</option>
<option value="MANAGER">Manager</option>
<option value="STAFF">Staff</option>
<option value="VIEWER">Viewer</option>
</select>
</div>
<label className="flex items-center gap-2">
<input type="checkbox" {...register("isActive")} />
Active user
</label>
<SubmitButton
isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful}
>
Update User
</SubmitButton>
</form>
)
}
function UserTextInput({
error,
id,
label,
placeholder,
register,
type = "text",
}: {
error?: string
id: string
label: string
placeholder: string
register: UseFormRegisterReturn
type?: string
}) {
return (
<div className="flex flex-col gap-2">
<label htmlFor={id} className="mb-2 block text-lg">
{label}
</label>
<input
type={type}
id={id}
placeholder={placeholder}
{...register}
className={`w-full rounded-lg border px-4 py-2 ${error ? "border-error" : ""}`}
/>
{error && <p className="text-error">{error}</p>}
</div>
)
}
@@ -1,145 +0,0 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import type { UseFormRegisterReturn } from "react-hook-form"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createUserAction } from "@/actions/user.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import {
type CreateUserFormType,
createUserSchema,
} from "@/schemas/user.schema"
export default function NewUserForm() {
const router = useRouter()
const {
register,
handleSubmit,
setError,
formState: { errors, isSubmitting, isSubmitSuccessful },
} = useForm<CreateUserFormType>({
resolver: zodResolver(createUserSchema),
defaultValues: {
role: "STAFF",
isActive: true,
},
})
const onSubmit = async (formData: CreateUserFormType) => {
const response = await createUserAction(formData)
if (response?.errors) {
Object.entries(response.errors).forEach(([fieldName, messages]) => {
messages.forEach((message: string) => {
setError(fieldName as keyof CreateUserFormType, {
type: "server",
message,
})
toast.error(message)
})
})
return
}
if (response?.success) {
toast.success(response.message)
router.push("/admin/users")
}
}
return (
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<UserTextInput
error={errors.name?.message}
id="name"
label="Name"
placeholder="Full name"
register={register("name")}
/>
<UserTextInput
error={errors.username?.message}
id="username"
label="Username"
placeholder="username"
register={register("username")}
/>
<UserTextInput
error={errors.email?.message}
id="email"
label="Email"
placeholder="user@example.com"
register={register("email")}
type="email"
/>
<UserTextInput
error={errors.password?.message}
id="password"
label="Password"
placeholder="Minimum 8 characters"
register={register("password")}
type="password"
/>
<RoleSelect register={register("role")} />
<SubmitButton
isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful}
>
Create User
</SubmitButton>
</form>
)
}
function UserTextInput({
error,
id,
label,
placeholder,
register,
type = "text",
}: {
error?: string
id: string
label: string
placeholder: string
register: UseFormRegisterReturn
type?: string
}) {
return (
<div className="flex flex-col gap-2">
<label htmlFor={id} className="mb-2 block text-lg">
{label}
</label>
<input
type={type}
id={id}
placeholder={placeholder}
{...register}
className={`w-full rounded-lg border px-4 py-2 ${error ? "border-error" : ""}`}
/>
{error && <p className="text-error">{error}</p>}
</div>
)
}
function RoleSelect({ register }: { register: UseFormRegisterReturn }) {
return (
<div className="flex flex-col gap-2">
<label htmlFor="role" className="mb-2 block text-lg">
Role
</label>
<select
id="role"
{...register}
className="w-full rounded-lg border px-4 py-2"
>
<option value="ADMIN">Admin</option>
<option value="MANAGER">Manager</option>
<option value="STAFF">Staff</option>
<option value="VIEWER">Viewer</option>
</select>
</div>
)
}
@@ -1,75 +0,0 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { resetUserPasswordAction } from "@/actions/user.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import {
type ResetUserPasswordFormType,
resetUserPasswordSchema,
} from "@/schemas/user.schema"
export default function ResetUserPasswordForm({ userId }: { userId: string }) {
const {
register,
handleSubmit,
reset,
setError,
formState: { errors, isSubmitting, isSubmitSuccessful },
} = useForm<ResetUserPasswordFormType>({
resolver: zodResolver(resetUserPasswordSchema),
defaultValues: {
id: userId,
},
})
const onSubmit = async (formData: ResetUserPasswordFormType) => {
const response = await resetUserPasswordAction(formData)
if (response?.errors) {
Object.entries(response.errors).forEach(([fieldName, messages]) => {
messages.forEach((message: string) => {
setError(fieldName as keyof ResetUserPasswordFormType, {
type: "server",
message,
})
toast.error(message)
})
})
return
}
if (response?.success) {
toast.success(response.message)
reset({ id: userId, password: "" })
}
}
return (
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<input type="hidden" {...register("id")} />
<div className="flex flex-col gap-2">
<label htmlFor="password" className="mb-2 block text-lg">
New password
</label>
<input
type="password"
id="password"
placeholder="Minimum 8 characters"
{...register("password")}
className={`w-full rounded-lg border px-4 py-2 ${errors.password ? "border-error" : ""}`}
/>
{errors.password && (
<p className="text-error">{errors.password.message}</p>
)}
</div>
<SubmitButton
isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful}
>
Reset Password
</SubmitButton>
</form>
)
}
@@ -1,12 +0,0 @@
import NewUserForm from "../_components/new.user.form"
export default function NewUserPage() {
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">New User</h1>
</div>
<NewUserForm />
</div>
)
}
-96
View File
@@ -1,96 +0,0 @@
import { Pencil } from "lucide-react"
import Link from "next/link"
import PageHeader from "@/components/common/pageheader"
import PaginationButtons from "@/components/common/pagination"
import { Button } from "@/components/ui/button"
import { getUsers } from "@/services/user.service"
export default async function UsersPage(props: {
searchParams?: Promise<{
page?: string
search?: string
}>
}) {
const searchParams = await props.searchParams
const currentPage = searchParams?.page ? parseInt(searchParams.page, 10) : 1
const search = searchParams?.search || ""
const { data: users, totalPages } = await getUsers({
page: currentPage,
pageSize: 10,
search,
})
return (
<div className="flex flex-col gap-4">
<PageHeader
title="Users"
link="/admin/users/new"
search={search}
data={users}
/>
{users.length === 0 && currentPage === 1 && (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4">
No users found.
</div>
</div>
)}
{users.length > 0 && (
<div className="overflow-x-auto">
<table className="text-muted-foreground w-full text-left text-sm">
<thead className="border-b">
<tr>
<th scope="col" className="p-4">
Name
</th>
<th scope="col" className="p-4">
Username
</th>
<th scope="col" className="p-4">
Email
</th>
<th scope="col" className="p-4">
Role
</th>
<th scope="col" className="p-4">
Status
</th>
<th scope="col" className="p-4">
Actions
</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id} className="border-b">
<td className="p-4">{user.name}</td>
<td className="p-4">{user.username}</td>
<td className="p-4">{user.email}</td>
<td className="p-4">{user.role}</td>
<td className="p-4">
{user.isActive ? "Active" : "Inactive"}
</td>
<td className="p-4">
<Link href={`/admin/users/${user.id}/edit`} passHref>
<Button variant="outline" size="icon">
<Pencil />
</Button>
</Link>
</td>
</tr>
))}
</tbody>
<tfoot className="border-t">
<tr>
<td colSpan={6} className="p-4 text-center text-sm">
<PaginationButtons totalPages={totalPages} />
</td>
</tr>
</tfoot>
</table>
</div>
)}
</div>
)
}
@@ -1,18 +1,18 @@
import type { UpdateAssignmentFormType } from "@/schemas/assignment.schema"
import { UpdateAssignmentFormType } from "@/lib/schemas/assignment.schemas"
import type { Item } from "@/lib/types"
import { AssetService } from "@/services/asset.service"
import { AssignmentService } from "@/services/assignment.service"
import { ItemService } from "@/services/item.service"
import { RecipientService } from "@/services/recipient.service"
import type { Item } from "@/types"
import AssignmentForm from "../../_components/edit.assignment.form"
export default async function EditAssignmentPage({
params,
}: {
params: Promise<{ assignmentId: string }>
params: Promise<{ assignamentId: string }>
}) {
const { assignmentId } = await params
const assignment = await AssignmentService.findById(assignmentId)
const { assignamentId } = await params
const assignment = await AssignmentService.findById(assignamentId)
const recipients = await RecipientService.findAll()
const items = await ItemService.findAllWithStock()
const assets = await AssetService.findAll()
@@ -4,13 +4,14 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateAssignment } from "@/actions/assignment.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { updateAssignment } from "@/lib/actions/assignament.actions"
import {
type UpdateAssignmentFormType,
UpdateAssignmentFormType,
updateAssignmentSchema,
} from "@/schemas/assignment.schema"
import type { Asset, Item, Recipient } from "@/types"
} from "@/lib/schemas/assignment.schemas"
import { Asset, Item, Recipient } from "@/lib/types"
interface Props {
recipients: Recipient[]
@@ -50,7 +51,7 @@ export default function EditAssignmentForm({
if (response?.errors) {
Object.values(response.errors as Record<string, string[]>).forEach(
(messages) => {
messages.forEach((msg) => void toast.error(msg))
messages.forEach((msg) => toast.error(msg))
},
)
return
@@ -5,13 +5,14 @@ import { useRouter } from "next/navigation"
import { useMemo } from "react"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createAssignment } from "@/actions/assignment.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { createAssignment } from "@/lib/actions/assignament.actions"
import {
type CreateAssignmentFormType,
CreateAssignmentFormType,
createAssignmentSchema,
} from "@/schemas/assignment.schema"
import type { Asset, Item, Recipient } from "@/types"
} from "@/lib/schemas/assignment.schemas"
import { Asset, Item, Recipient } from "@/lib/types"
interface Props {
recipients: Recipient[]
@@ -49,7 +50,7 @@ export default function CreateAssignmentForm({
if (response?.errors) {
Object.values(response.errors as Record<string, string[]>).forEach(
(messages) => {
messages.forEach((msg) => void toast.error(msg))
messages.forEach((msg) => toast.error(msg))
},
)
return
@@ -4,9 +4,10 @@ import { ArrowLeft } from "lucide-react"
import { useRouter } from "next/navigation"
import { useTransition } from "react"
import { toast } from "sonner"
import { returnAssignment } from "@/actions/assignment.actions"
import { Button } from "@/components/ui/button"
import type { ReturnAssignmentFormType } from "@/schemas/assignment.schema"
import { returnAssignment } from "@/lib/actions/assignament.actions"
import { ReturnAssignmentFormType } from "@/lib/schemas/assignment.schemas"
export default function ReturnButton({
assignmentId,
+2 -8
View File
@@ -15,7 +15,7 @@ export default async function AssignmentsPage(props: {
}>
}) {
const searchParams = await props.searchParams
const currentPage = searchParams?.page ? parseInt(searchParams.page, 10) : 1
const currentPage = searchParams?.page ? parseInt(searchParams.page) : 1
const search = searchParams?.search || ""
const { data: assignments, totalPages } =
await AssignmentService.findAllWithRecipientPaginated({
@@ -46,9 +46,6 @@ export default async function AssignmentsPage(props: {
<th scope="col" className="p-4">
Serial Number
</th>
<th scope="col" className="p-4">
Quantity
</th>
<th scope="col" className="p-4">
Actions
</th>
@@ -77,9 +74,6 @@ export default async function AssignmentsPage(props: {
<td className="p-4">
{assignment?.asset?.serialNumber || "N/A"}
</td>
<td className="p-4">
{assignment?.quantity}
</td>
<td className="p-4">
<div className="flex gap-2">
<Link
@@ -98,7 +92,7 @@ export default async function AssignmentsPage(props: {
</tbody>
<tfoot className="border-t">
<tr>
<td colSpan={5} className="p-4 text-center text-sm">
<td colSpan={4} className="p-4 text-center text-sm">
<PaginationButtons totalPages={totalPages} />
</td>
</tr>
@@ -2,13 +2,14 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import type { ChangeEvent } from "react"
import { ChangeEvent } from "react"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { importItems } from "@/actions/import.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { type ImportFormType, importSchema } from "@/schemas/import.schema"
import type { CategorySummary } from "@/types"
import { importItems } from "@/lib/actions/import.actions"
import { ImportFormType, importSchema } from "@/lib/schemas/import.schemas"
import { CategorySummary } from "@/lib/types"
export default function ImportForm({
categories,
+1 -1
View File
@@ -15,7 +15,7 @@ export default async function ImportPage() {
<h1 className="text-2xl font-bold">Mass Import</h1>
</div>
<div className="flex items-center justify-end gap-4">
{(ENVIRONMENT === "development" || ENVIRONMENT === "demo") && (
{ENVIRONMENT === "demo" && (
<Link href="/sample_data.csv" download>
<Button variant="outline">
<Download />
@@ -1,9 +1,9 @@
"use server"
import { AssetWithAssignment } from "@/lib/types"
import { AssetService } from "@/services/asset.service"
import { ItemService } from "@/services/item.service"
import { RecipientService } from "@/services/recipient.service"
import type { AssetWithAssignment } from "@/types"
import EditAssetForm from "../../_components/edit.asset.form"
@@ -4,19 +4,20 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateAssetAction } from "@/actions/asset.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { ITEM_STATUS } from "@/lib/constants"
import { ItemStatus } from "@/generated/prisma/client"
import { updateAssetAction } from "@/lib/actions/asset.actions"
import {
type UpdateAssetFormType,
UpdateAssetFormType,
updateAssetSchema,
} from "@/schemas/asset.schema"
import type {
} from "@/lib/schemas/asset.schemas"
import {
AssetWithAssignment,
Item,
Recipient,
UpdateAssetStatus,
} from "@/types"
} from "@/lib/types"
interface EditAssetFormProps {
asset: AssetWithAssignment
@@ -41,11 +42,11 @@ export default function EditAssetForm({
resolver: zodResolver(updateAssetSchema),
defaultValues: {
id: asset.id,
itemId: asset.itemId ?? undefined,
itemId: asset.itemId ?? "",
serialNumber: asset.serialNumber,
deliveryNote: asset.deliveryNote ?? undefined,
deliveryNote: asset.deliveryNote ?? "",
status: asset.status as UpdateAssetStatus,
recipientId: asset.assignment?.recipientId ?? undefined,
recipientId: asset.assignment?.recipientId ?? "",
},
shouldFocusError: true,
mode: "onSubmit",
@@ -137,7 +138,7 @@ export default function EditAssetForm({
className="w-full rounded-lg border px-4 py-2"
>
<option value="">Select a status</option>
{Object.values(ITEM_STATUS).map((status) => (
{Object.values(ItemStatus).map((status) => (
<option key={status} value={status}>
{status}
</option>
@@ -4,14 +4,15 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createAssetAction } from "@/actions/asset.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { ITEM_STATUS } from "@/lib/constants"
import { ItemStatus } from "@/generated/prisma/client"
import { createAssetAction } from "@/lib/actions/asset.actions"
import {
type CreateAssetFormType,
CreateAssetFormType,
createAssetSchema,
} from "@/schemas/asset.schema"
import type { ItemWithoutStock, Recipient } from "@/types"
} from "@/lib/schemas/asset.schemas"
import { ItemWithoutStock, Recipient } from "@/lib/types"
interface NewAssetFormProps {
items: ItemWithoutStock[]
@@ -122,7 +123,7 @@ export default function NewAssetForm({ items, recipients }: NewAssetFormProps) {
className="w-full rounded-lg border px-4 py-2"
>
<option value="">Select a status</option>
{Object.values(ITEM_STATUS).map((status) => (
{Object.values(ItemStatus).map((status) => (
<option key={status} value={status}>
{status}
</option>
@@ -13,7 +13,7 @@ export default async function AssetsPage(props: {
}>
}) {
const searchParams = await props.searchParams
const currentPage = searchParams?.page ? parseInt(searchParams.page, 10) : 1
const currentPage = searchParams?.page ? parseInt(searchParams.page) : 1
const search = searchParams?.search || ""
const { data: assets, totalPages } =
await AssetService.findAllWithItemAndCategory({
@@ -4,8 +4,9 @@ import { Trash } from "lucide-react"
import { useRouter } from "next/navigation"
import { useTransition } from "react"
import { toast } from "sonner"
import { deleteCategoryAction } from "@/actions/category.actions"
import { Button } from "@/components/ui/button"
import { deleteCategoryAction } from "@/lib/actions/category.actions"
export default function DeleteCategoryButton({
categoryId,
@@ -4,13 +4,14 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateCategoryAction } from "@/actions/category.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { updateCategoryAction } from "@/lib/actions/category.actions"
import {
type UpdateCategoryFormType,
UpdateCategoryFormType,
updateCategorySchema,
} from "@/schemas/category.schema"
import type { CategorySummary } from "@/types"
} from "@/lib/schemas/category.schemas"
import { CategorySummary } from "@/lib/types"
export default function EditCategoryForm({
category,
@@ -4,12 +4,13 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createCategoryAction } from "@/actions/category.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { createCategoryAction } from "@/lib/actions/category.actions"
import {
type CreateCategoryFormType,
CreateCategoryFormType,
createCategorySchema,
} from "@/schemas/category.schema"
} from "@/lib/schemas/category.schemas"
export default function NewCategoryForm() {
const router = useRouter()
@@ -15,7 +15,7 @@ export default async function Items(props: {
}>
}) {
const searchParams = await props.searchParams
const currentPage = searchParams?.page ? parseInt(searchParams.page, 10) : 1
const currentPage = searchParams?.page ? parseInt(searchParams.page) : 1
const search = searchParams?.search || ""
const { data: categories, totalPages } =
await CategoryService.findAllWithItemsCountPaginated({
@@ -4,8 +4,9 @@ import { Trash } from "lucide-react"
import { useRouter } from "next/navigation"
import { useTransition } from "react"
import { toast } from "sonner"
import { deleteItemAction } from "@/actions/item.actions"
import { Button } from "@/components/ui/button"
import { deleteItemAction } from "@/lib/actions/item.actions"
export default function DeleteItemButton({ itemId }: { itemId: string }) {
const router = useRouter()
@@ -4,13 +4,14 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { createItemAction } from "@/actions/item.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { createItemAction } from "@/lib/actions/item.actions"
import {
type CreateItemFormType,
CreateItemFormType,
createItemSchema,
} from "@/schemas/item.schema"
import type { CategorySummary } from "@/types"
} from "@/lib/schemas/item.schemas"
import { CategorySummary } from "@/lib/types"
export default function NewItemForm({
categories,
@@ -4,13 +4,14 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { updateItemAction } from "@/actions/item.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { updateItemAction } from "@/lib/actions/item.actions"
import {
type UpdateItemFormType,
UpdateItemFormType,
updateItemSchema,
} from "@/schemas/item.schema"
import type { CategorySummary, ItemWithAssetCount } from "@/types"
} from "@/lib/schemas/item.schemas"
import { CategorySummary, ItemWithAssetCount } from "@/lib/types"
export default function UpdateItemForm({
categories,
+1 -1
View File
@@ -15,7 +15,7 @@ export default async function ItemsPage(props: {
}>
}) {
const searchParams = await props.searchParams
const currentPage = searchParams?.page ? parseInt(searchParams.page, 10) : 1
const currentPage = searchParams?.page ? parseInt(searchParams.page) : 1
const search = searchParams?.search || ""
const { data: items, totalPages } = await ItemService.findAllWithAssetCount({
page: currentPage,
+1 -4
View File
@@ -3,18 +3,15 @@ import { Toaster } from "sonner"
import Navbar from "@/components/layout/navbar"
import AppSidebar from "@/components/layout/sidebar"
import { SidebarProvider } from "@/components/ui/sidebar"
import { auth } from "@/lib/auth"
export default async function LayoutDashboard({
children,
}: {
children: React.ReactNode
}) {
const session = await auth()
return (
<SidebarProvider>
<AppSidebar userRole={session?.user.role} />
<AppSidebar />
<main className="w-full">
<Navbar />
<div className="flex-1 p-6">{children}</div>
+1 -1
View File
@@ -8,7 +8,7 @@ export default async function MovementsPage(props: {
}>
}) {
const searchParams = await props.searchParams
const currentPage = searchParams?.page ? parseInt(searchParams.page, 10) : 1
const currentPage = searchParams?.page ? parseInt(searchParams.page) : 1
const { data: movements, totalPages } = await MovementService.findAll({
page: currentPage,
pageSize: 12,
@@ -20,7 +20,7 @@ export default async function RecipientInfoPage({
<Card className="rounded-sm shadow-none">
<CardHeader>
<CardTitle>
{`${recipient.firstName} ${recipient.lastName}`}
{recipient.firstName + " " + recipient.lastName}
</CardTitle>
</CardHeader>
<CardContent>
@@ -4,18 +4,19 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { SubmitButton } from "@/components/forms/submitButton"
import { RecipientDepartment } from "@/generated/prisma/client"
import {
createNewRecipient,
updateRecipient,
} from "@/actions/recipient.actions"
import { SubmitButton } from "@/components/forms/submitButton"
import { RECIPIENT_DEPARTMENTS } from "@/lib/constants"
} from "@/lib/actions/recipient.actions"
import {
type CreateRecipientFormType,
CreateRecipientFormType,
recipientSchema,
type UpdateRecipientFormType,
} from "@/schemas/recipient.schema"
import type { Recipient } from "@/types"
UpdateRecipientFormType,
} from "@/lib/schemas/recipients.schemas"
import { Recipient } from "@/lib/types"
interface RecipientFormProps {
initialData?: Recipient
@@ -129,7 +130,7 @@ export default function RecipientForm({
className="w-full rounded-lg border px-4 py-2"
>
<option value="">Select a department</option>
{Object.keys(RECIPIENT_DEPARTMENTS).map((department) => (
{Object.keys(RecipientDepartment).map((department) => (
<option key={department} value={department}>
{department}
</option>
+3 -3
View File
@@ -4,7 +4,7 @@ import Link from "next/link"
import PageHeader from "@/components/common/pageheader"
import PaginationButtons from "@/components/common/pagination"
import { Button } from "@/components/ui/button"
import type { Recipient } from "@/generated/prisma/client"
import { Recipient } from "@/generated/prisma/client"
import { RecipientService } from "@/services/recipient.service"
export default async function RecipientsPage(props: {
@@ -14,7 +14,7 @@ export default async function RecipientsPage(props: {
}>
}) {
const searchParams = await props.searchParams
const currentPage = searchParams?.page ? parseInt(searchParams.page, 10) : 1
const currentPage = searchParams?.page ? parseInt(searchParams.page) : 1
const search = searchParams?.search || ""
const { data: recipients, totalPages } =
await RecipientService.findAllPaginated({
@@ -62,7 +62,7 @@ export default async function RecipientsPage(props: {
<tr key={recipient.id} className="border-b">
<td className="p-4">{recipient.username}</td>
<td className="p-4">
{`${recipient.firstName} ${recipient.lastName}`}
{recipient.firstName + " " + recipient.lastName}
</td>
<td className="p-4">{recipient.email}</td>
<td className="p-4">{recipient.phone}</td>
+1 -1
View File
@@ -1,4 +1,4 @@
import { exec } from "node:child_process"
import { exec } from "child_process"
import { NextResponse } from "next/server"
import { verifyUserRole } from "@/services/auth.service"
-11
View File
@@ -1,11 +0,0 @@
import Link from "next/link"
export default function ForbiddenPage() {
return (
<main>
<h1>Acceso denegado</h1>
<p>No tienes permisos para acceder a esta sección.</p>
<Link href="/">Volver al inicio</Link>
</main>
)
}
+1 -2
View File
@@ -1,13 +1,12 @@
import { Button } from "@/components/ui/button"
import { signOut } from "@/lib/auth"
import { SIGN_IN_URL } from "@/lib/constants"
export function SignOut() {
return (
<form
action={async () => {
"use server"
await signOut({ redirectTo: SIGN_IN_URL })
await signOut()
}}
>
<Button type="submit" variant="destructive">
+1 -1
View File
@@ -8,7 +8,7 @@ interface PageHeaderProps {
title?: string
link?: string
search?: string
data: unknown[]
data: any[]
}
export default function PageHeader({
+1 -1
View File
@@ -10,7 +10,7 @@ import { Input } from "../ui/input"
interface SearchProps {
paramKey?: string
placeholder?: string
[x: string]: unknown
[x: string]: any
}
export default function Search({
-1
View File
@@ -31,7 +31,6 @@ export default function ResetButton() {
return (
<button
type="button"
className="flex cursor-pointer items-center gap-2 rounded-sm bg-red-500 px-2 py-1.5 text-sm text-white outline-hidden hover:bg-red-600"
onClick={handleReset}
disabled={loading}
+5 -19
View File
@@ -5,12 +5,12 @@ import {
Clipboard,
Home,
Package,
Shield,
ShoppingCart,
User,
} from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
Sidebar,
SidebarContent,
@@ -21,7 +21,6 @@ import {
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
import type { UserRole } from "@/generated/prisma/client"
import { SidebarSection } from "./sidebar/sidebarSection"
@@ -73,22 +72,9 @@ const items = [
]
export default function AppSidebar({
userRole,
...props
}: React.ComponentProps<typeof Sidebar> & { userRole?: UserRole }) {
}: React.ComponentProps<typeof Sidebar>) {
const pathname = usePathname()
const visibleItems =
userRole === "ADMIN"
? [
...items,
{
type: "item",
title: "Users",
url: "/admin/users",
icon: Shield,
},
]
: items
return (
<Sidebar {...props}>
@@ -102,7 +88,7 @@ export default function AppSidebar({
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{visibleItems.map((item) => {
{items.map((item, index) => {
if (item.type === "item") {
const isActive =
item.url === "/"
@@ -110,7 +96,7 @@ export default function AppSidebar({
: pathname.startsWith(item.url)
return (
<SidebarMenuItem key={`item-${item.title}`}>
<SidebarMenuItem key={`item-${index}`}>
<SidebarMenuButton asChild isActive={isActive}>
<Link href={item.url}>
<item.icon className="mr-2 h-4 w-4" />
@@ -123,7 +109,7 @@ export default function AppSidebar({
if (item.type === "section") {
return (
<SidebarSection
key={`section-${item.title}`}
key={`section-${index}`}
title={item.title}
icon={item.icon}
items={item.items}
@@ -3,8 +3,7 @@
import { ChevronRight } from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import type React from "react"
import { useEffect, useState } from "react"
import React, { useEffect, useState } from "react"
import {
Collapsible,
@@ -34,7 +33,7 @@ export function SidebarSection({
useEffect(() => {
setOpen(isAnySubActive)
}, [isAnySubActive])
}, [isAnySubActive, pathname])
return (
<Collapsible
@@ -55,7 +54,7 @@ export function SidebarSection({
{items?.map((subItem, i) => {
const isActive = pathname.startsWith(subItem.url)
return (
<SidebarMenuSubItem key={i++}>
<SidebarMenuSubItem key={i}>
<SidebarMenuSubButton asChild isActive={isActive}>
<Link href={subItem.url}>{subItem.title}</Link>
</SidebarMenuSubButton>
+8 -14
View File
@@ -1,19 +1,19 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
@@ -22,13 +22,9 @@ const buttonVariants = cva(
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
@@ -40,21 +36,19 @@ const buttonVariants = cva(
function Button({
className,
variant = "default",
size = "default",
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
+10 -10
View File
@@ -7,8 +7,8 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card"
className={cn(
"flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
className
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className,
)}
{...props}
/>
@@ -20,8 +20,8 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className,
)}
{...props}
/>
@@ -42,7 +42,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-sm text-muted-foreground", className)}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
@@ -54,7 +54,7 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
className,
)}
{...props}
/>
@@ -83,10 +83,10 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
}
+3 -3
View File
@@ -1,8 +1,8 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { CheckIcon } from "lucide-react"
import { Checkbox as CheckboxPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
@@ -14,14 +14,14 @@ function Checkbox({
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer size-4 shrink-0 rounded-[4px] border border-input shadow-xs transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary",
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none"
className="flex items-center justify-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
+2 -2
View File
@@ -1,6 +1,6 @@
"use client"
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
function Collapsible({
...props
@@ -30,4 +30,4 @@ function CollapsibleContent({
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
export { Collapsible, CollapsibleContent, CollapsibleTrigger }
+15 -38
View File
@@ -1,11 +1,10 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import { Dialog as DialogPrimitive } from "radix-ui"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
function Dialog({
...props
@@ -39,8 +38,8 @@ function DialogOverlay({
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
className
"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-black/50",
className,
)}
{...props}
/>
@@ -50,32 +49,24 @@ function DialogOverlay({
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
className
"bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
)
@@ -91,30 +82,16 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
)
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean
}) {
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close asChild>
<Button variant="outline">Close</Button>
</DialogPrimitive.Close>
)}
</div>
/>
)
}
@@ -138,7 +115,7 @@ function DialogDescription({
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-sm text-muted-foreground", className)}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
+23 -23
View File
@@ -1,8 +1,8 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
import * as React from "react"
import { cn } from "@/lib/utils"
@@ -42,8 +42,8 @@ function DropdownMenuContent({
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
className
"bg-popover text-popover-foreground 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 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className,
)}
{...props}
/>
@@ -74,8 +74,8 @@ function DropdownMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
className
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
@@ -92,8 +92,8 @@ function DropdownMenuCheckboxItem({
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
@@ -128,8 +128,8 @@ function DropdownMenuRadioItem({
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
@@ -156,7 +156,7 @@ function DropdownMenuLabel({
data-inset={inset}
className={cn(
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className
className,
)}
{...props}
/>
@@ -170,7 +170,7 @@ function DropdownMenuSeparator({
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("-mx-1 my-1 h-px bg-border", className)}
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
)
@@ -184,8 +184,8 @@ function DropdownMenuShortcut({
<span
data-slot="dropdown-menu-shortcut"
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
"text-muted-foreground ml-auto text-xs tracking-widest",
className,
)}
{...props}
/>
@@ -211,8 +211,8 @@ function DropdownMenuSubTrigger({
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
className
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
className,
)}
{...props}
>
@@ -230,8 +230,8 @@ function DropdownMenuSubContent({
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
className
"bg-popover text-popover-foreground 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] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className,
)}
{...props}
/>
@@ -240,18 +240,18 @@ function DropdownMenuSubContent({
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
}
+3 -3
View File
@@ -8,9 +8,9 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot="input"
className={cn(
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
{...props}
+1 -1
View File
@@ -6,7 +6,7 @@ import {
} from "lucide-react"
import { cn } from "@/lib/utils"
import { buttonVariants, type Button } from "@/components/ui/button"
import { Button, buttonVariants } from "@/components/ui/button"
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
return (
+4 -4
View File
@@ -1,7 +1,7 @@
"use client"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import * as React from "react"
import { Separator as SeparatorPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
@@ -13,12 +13,12 @@ function Separator({
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
data-slot="separator-root"
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className,
)}
{...props}
/>
+20 -24
View File
@@ -1,8 +1,8 @@
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import { Dialog as SheetPrimitive } from "radix-ui"
import * as React from "react"
import { cn } from "@/lib/utils"
@@ -36,8 +36,8 @@ function SheetOverlay({
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn(
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
className
"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-black/50",
className,
)}
{...props}
/>
@@ -48,11 +48,9 @@ function SheetContent({
className,
children,
side = "right",
showCloseButton = true,
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left"
showCloseButton?: boolean
}) {
return (
<SheetPortal>
@@ -60,26 +58,24 @@ function SheetContent({
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
"fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
side === "right" &&
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
side === "left" &&
"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
side === "top" &&
"inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
side === "bottom" &&
"inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
className
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
)}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
)
@@ -112,7 +108,7 @@ function SheetTitle({
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn("font-semibold text-foreground", className)}
className={cn("text-foreground font-semibold", className)}
{...props}
/>
)
@@ -125,7 +121,7 @@ function SheetDescription({
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn("text-sm text-muted-foreground", className)}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
@@ -133,11 +129,11 @@ function SheetDescription({
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
}
+45 -45
View File
@@ -1,12 +1,10 @@
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "@radix-ui/react-slot"
import { cva, VariantProps } from "class-variance-authority"
import { PanelLeftIcon } from "lucide-react"
import { Slot } from "radix-ui"
import * as React from "react"
import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
@@ -24,6 +22,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils"
const SIDEBAR_COOKIE_NAME = "sidebar_state"
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
@@ -85,7 +85,7 @@ function SidebarProvider({
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
[setOpenProp, open]
[setOpenProp, open],
)
// Helper to toggle the sidebar.
@@ -123,7 +123,7 @@ function SidebarProvider({
setOpenMobile,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
)
return (
@@ -139,8 +139,8 @@ function SidebarProvider({
} as React.CSSProperties
}
className={cn(
"group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
className
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
className,
)}
{...props}
>
@@ -170,8 +170,8 @@ function Sidebar({
<div
data-slot="sidebar"
className={cn(
"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
className
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
className,
)}
{...props}
>
@@ -187,7 +187,7 @@ function Sidebar({
data-sidebar="sidebar"
data-slot="sidebar"
data-mobile="true"
className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
@@ -207,7 +207,7 @@ function Sidebar({
return (
<div
className="group peer hidden text-sidebar-foreground md:block"
className="group peer text-sidebar-foreground hidden md:block"
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
@@ -223,7 +223,7 @@ function Sidebar({
"group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
)}
/>
<div
@@ -237,14 +237,14 @@ function Sidebar({
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
className
className,
)}
{...props}
>
<div
data-sidebar="sidebar"
data-slot="sidebar-inner"
className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm"
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
>
{children}
</div>
@@ -291,13 +291,13 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex",
"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
"hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className
className,
)}
{...props}
/>
@@ -309,9 +309,9 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
<main
data-slot="sidebar-inset"
className={cn(
"relative flex w-full flex-1 flex-col bg-background",
"bg-background relative flex w-full flex-1 flex-col",
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
className
className,
)}
{...props}
/>
@@ -326,7 +326,7 @@ function SidebarInput({
<Input
data-slot="sidebar-input"
data-sidebar="input"
className={cn("h-8 w-full bg-background shadow-none", className)}
className={cn("bg-background h-8 w-full shadow-none", className)}
{...props}
/>
)
@@ -362,7 +362,7 @@ function SidebarSeparator({
<Separator
data-slot="sidebar-separator"
data-sidebar="separator"
className={cn("mx-2 w-auto bg-sidebar-border", className)}
className={cn("bg-sidebar-border mx-2 w-auto", className)}
{...props}
/>
)
@@ -375,7 +375,7 @@ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
data-sidebar="content"
className={cn(
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className
className,
)}
{...props}
/>
@@ -398,16 +398,16 @@ function SidebarGroupLabel({
asChild = false,
...props
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "div"
const Comp = asChild ? Slot : "div"
return (
<Comp
data-slot="sidebar-group-label"
data-sidebar="group-label"
className={cn(
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className
className,
)}
{...props}
/>
@@ -419,18 +419,18 @@ function SidebarGroupAction({
asChild = false,
...props
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "button"
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="sidebar-group-action"
data-sidebar="group-action"
className={cn(
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 md:after:hidden",
"group-data-[collapsible=icon]:hidden",
className
className,
)}
{...props}
/>
@@ -474,13 +474,13 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
}
const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},
size: {
default: "h-8 text-sm",
@@ -492,7 +492,7 @@ const sidebarMenuButtonVariants = cva(
variant: "default",
size: "default",
},
}
},
)
function SidebarMenuButton({
@@ -508,7 +508,7 @@ function SidebarMenuButton({
isActive?: boolean
tooltip?: string | React.ComponentProps<typeof TooltipContent>
} & VariantProps<typeof sidebarMenuButtonVariants>) {
const Comp = asChild ? Slot.Root : "button"
const Comp = asChild ? Slot : "button"
const { isMobile, state } = useSidebar()
const button = (
@@ -554,14 +554,14 @@ function SidebarMenuAction({
asChild?: boolean
showOnHover?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="sidebar-menu-action"
data-sidebar="menu-action"
className={cn(
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform peer-hover/menu-button:text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 md:after:hidden",
"peer-data-[size=sm]/menu-button:top-1",
@@ -569,8 +569,8 @@ function SidebarMenuAction({
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
showOnHover &&
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground data-[state=open]:opacity-100 md:opacity-0",
className
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
className,
)}
{...props}
/>
@@ -586,13 +586,13 @@ function SidebarMenuBadge({
data-slot="sidebar-menu-badge"
data-sidebar="menu-badge"
className={cn(
"pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none",
"text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none",
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
className
className,
)}
{...props}
/>
@@ -643,9 +643,9 @@ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
data-slot="sidebar-menu-sub"
data-sidebar="menu-sub"
className={cn(
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
"group-data-[collapsible=icon]:hidden",
className
className,
)}
{...props}
/>
@@ -677,7 +677,7 @@ function SidebarMenuSubButton({
size?: "sm" | "md"
isActive?: boolean
}) {
const Comp = asChild ? Slot.Root : "a"
const Comp = asChild ? Slot : "a"
return (
<Comp
@@ -686,12 +686,12 @@ function SidebarMenuSubButton({
data-size={size}
data-active={isActive}
className={cn(
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
"text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
className
className,
)}
{...props}
/>
+1 -1
View File
@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="skeleton"
className={cn("animate-pulse rounded-md bg-accent", className)}
className={cn("bg-accent animate-pulse rounded-md", className)}
{...props}
/>
)
+1 -16
View File
@@ -1,14 +1,7 @@
"use client"
import {
CircleCheckIcon,
InfoIcon,
Loader2Icon,
OctagonXIcon,
TriangleAlertIcon,
} from "lucide-react"
import { useTheme } from "next-themes"
import { Toaster as Sonner, type ToasterProps } from "sonner"
import { Toaster as Sonner, ToasterProps } from "sonner"
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
@@ -17,19 +10,11 @@ const Toaster = ({ ...props }: ToasterProps) => {
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
icons={{
success: <CircleCheckIcon className="size-4" />,
info: <InfoIcon className="size-4" />,
warning: <TriangleAlertIcon className="size-4" />,
error: <OctagonXIcon className="size-4" />,
loading: <Loader2Icon className="size-4 animate-spin" />,
}}
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
"--border-radius": "var(--radius)",
} as React.CSSProperties
}
{...props}
+10 -6
View File
@@ -1,7 +1,7 @@
"use client"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import * as React from "react"
import { Tooltip as TooltipPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
@@ -21,7 +21,11 @@ function TooltipProvider({
function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
return (
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
)
}
function TooltipTrigger({
@@ -42,16 +46,16 @@ function TooltipContent({
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
className
"bg-primary text-primary-foreground 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 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
className,
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
)
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
+1
View File
@@ -0,0 +1 @@
export * from "./index"
+4
View File
@@ -0,0 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
module.exports = { ...require('.') }
+1
View File
@@ -0,0 +1 @@
export * from "./index"
+4
View File
@@ -0,0 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
module.exports = { ...require('.') }
+1
View File
@@ -0,0 +1 @@
export * from "./default"
File diff suppressed because one or more lines are too long
@@ -0,0 +1,301 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
Object.defineProperty(exports, "__esModule", { value: true });
const {
Decimal,
objectEnumValues,
makeStrictEnum,
Public,
getRuntime,
skip
} = require('./runtime/index-browser.js')
const Prisma = {}
exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.10.1
* Query Engine version: 9b628578b3b7cae625e8c927178f15a170e74a9c
*/
Prisma.prismaVersion = {
client: "6.10.1",
engine: "9b628578b3b7cae625e8c927178f15a170e74a9c"
}
Prisma.PrismaClientKnownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)};
Prisma.PrismaClientUnknownRequestError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientRustPanicError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientInitializationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.PrismaClientValidationError = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.Decimal = Decimal
/**
* Re-export of sql-template-tag
*/
Prisma.sql = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.empty = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.join = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.raw = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.validator = Public.validator
/**
* Extensions
*/
Prisma.getExtensionContext = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
Prisma.defineExtension = () => {
const runtimeName = getRuntime().prettyName;
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
)}
/**
* Shorthand utilities for JSON filtering
*/
Prisma.DbNull = objectEnumValues.instances.DbNull
Prisma.JsonNull = objectEnumValues.instances.JsonNull
Prisma.AnyNull = objectEnumValues.instances.AnyNull
Prisma.NullTypes = {
DbNull: objectEnumValues.classes.DbNull,
JsonNull: objectEnumValues.classes.JsonNull,
AnyNull: objectEnumValues.classes.AnyNull
}
/**
* Enums
*/
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
});
exports.Prisma.UserScalarFieldEnum = {
id: 'id',
username: 'username',
name: 'name',
email: 'email',
password: 'password',
role: 'role',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.RecipientScalarFieldEnum = {
id: 'id',
username: 'username',
firstName: 'firstName',
lastName: 'lastName',
department: 'department',
email: 'email',
phone: 'phone',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.CategoryScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
isActive: 'isActive',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.ItemScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
categoryId: 'categoryId',
stock: 'stock',
minStock: 'minStock',
maxStock: 'maxStock',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
deletedAt: 'deletedAt'
};
exports.Prisma.AssetScalarFieldEnum = {
id: 'id',
itemId: 'itemId',
serialNumber: 'serialNumber',
deliveryNote: 'deliveryNote',
status: 'status',
notes: 'notes',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.AssignmentScalarFieldEnum = {
id: 'id',
quantity: 'quantity',
notes: 'notes',
itemId: 'itemId',
assetId: 'assetId',
recipientId: 'recipientId',
assignmentDate: 'assignmentDate',
returnDate: 'returnDate',
createdBy: 'createdBy',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
exports.Prisma.MovementScalarFieldEnum = {
id: 'id',
type: 'type',
quantity: 'quantity',
details: 'details',
notes: 'notes',
itemId: 'itemId',
assetId: 'assetId',
previousStock: 'previousStock',
newStock: 'newStock',
recipientId: 'recipientId',
assignmentId: 'assignmentId',
userId: 'userId',
createdAt: 'createdAt'
};
exports.Prisma.SortOrder = {
asc: 'asc',
desc: 'desc'
};
exports.Prisma.QueryMode = {
default: 'default',
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.UserRole = exports.$Enums.UserRole = {
ADMIN: 'ADMIN',
MANAGER: 'MANAGER',
STAFF: 'STAFF',
VIEWER: 'VIEWER'
};
exports.RecipientDepartment = exports.$Enums.RecipientDepartment = {
IT: 'IT',
ENGINEERING: 'ENGINEERING',
LOGISTICS: 'LOGISTICS',
TRAFFIC: 'TRAFFIC',
DRIVER: 'DRIVER',
ADMINISTRATION: 'ADMINISTRATION',
SALES: 'SALES',
OTHER: 'OTHER'
};
exports.ItemStatus = exports.$Enums.ItemStatus = {
AVAILABLE: 'AVAILABLE',
ASSIGNED: 'ASSIGNED',
RESERVED: 'RESERVED',
IN_REPAIR: 'IN_REPAIR',
BROKEN: 'BROKEN',
STOLEN: 'STOLEN',
DISPOSED: 'DISPOSED'
};
exports.MovementType = exports.$Enums.MovementType = {
IN: 'IN',
OUT: 'OUT',
ASSIGNMENT: 'ASSIGNMENT',
RETURN: 'RETURN',
ADJUSTMENT: 'ADJUSTMENT',
DELETED: 'DELETED'
};
exports.Prisma.ModelName = {
User: 'User',
Recipient: 'Recipient',
Category: 'Category',
Item: 'Item',
Asset: 'Asset',
Assignment: 'Assignment',
Movement: 'Movement'
};
/**
* This is a stub Prisma Client that will error at runtime if called.
*/
class PrismaClient {
constructor() {
return new Proxy(this, {
get(target, prop) {
let message
const runtime = getRuntime()
if (runtime.isEdge) {
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
- Use Prisma Accelerate: https://pris.ly/d/accelerate
- Use Driver Adapters: https://pris.ly/d/driver-adapters
`;
} else {
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
}
message += `
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
throw new Error(message)
}
})
}
}
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+146
View File
@@ -0,0 +1,146 @@
{
"name": "prisma-client-8fa07f1ca1555b6abbe80c6f50fa3e992025f58ff8812e13aca6a56a4a773a8c",
"main": "index.js",
"types": "index.d.ts",
"browser": "index-browser.js",
"exports": {
"./client": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./package.json": "./package.json",
".": {
"require": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"import": {
"node": "./index.js",
"edge-light": "./wasm.js",
"workerd": "./wasm.js",
"worker": "./wasm.js",
"browser": "./index-browser.js",
"default": "./index.js"
},
"default": "./index.js"
},
"./edge": {
"types": "./edge.d.ts",
"require": "./edge.js",
"import": "./edge.js",
"default": "./edge.js"
},
"./react-native": {
"types": "./react-native.d.ts",
"require": "./react-native.js",
"import": "./react-native.js",
"default": "./react-native.js"
},
"./extension": {
"types": "./extension.d.ts",
"require": "./extension.js",
"import": "./extension.js",
"default": "./extension.js"
},
"./index-browser": {
"types": "./index.d.ts",
"require": "./index-browser.js",
"import": "./index-browser.js",
"default": "./index-browser.js"
},
"./index": {
"types": "./index.d.ts",
"require": "./index.js",
"import": "./index.js",
"default": "./index.js"
},
"./wasm": {
"types": "./wasm.d.ts",
"require": "./wasm.js",
"import": "./wasm.mjs",
"default": "./wasm.mjs"
},
"./runtime/client": {
"types": "./runtime/client.d.ts",
"require": "./runtime/client.js",
"import": "./runtime/client.mjs",
"default": "./runtime/client.mjs"
},
"./runtime/library": {
"types": "./runtime/library.d.ts",
"require": "./runtime/library.js",
"import": "./runtime/library.mjs",
"default": "./runtime/library.mjs"
},
"./runtime/binary": {
"types": "./runtime/binary.d.ts",
"require": "./runtime/binary.js",
"import": "./runtime/binary.mjs",
"default": "./runtime/binary.mjs"
},
"./runtime/wasm-engine-edge": {
"types": "./runtime/wasm-engine-edge.d.ts",
"require": "./runtime/wasm-engine-edge.js",
"import": "./runtime/wasm-engine-edge.mjs",
"default": "./runtime/wasm-engine-edge.mjs"
},
"./runtime/wasm-compiler-edge": {
"types": "./runtime/wasm-compiler-edge.d.ts",
"require": "./runtime/wasm-compiler-edge.js",
"import": "./runtime/wasm-compiler-edge.mjs",
"default": "./runtime/wasm-compiler-edge.mjs"
},
"./runtime/edge": {
"types": "./runtime/edge.d.ts",
"require": "./runtime/edge.js",
"import": "./runtime/edge-esm.js",
"default": "./runtime/edge-esm.js"
},
"./runtime/react-native": {
"types": "./runtime/react-native.d.ts",
"require": "./runtime/react-native.js",
"import": "./runtime/react-native.js",
"default": "./runtime/react-native.js"
},
"./generator-build": {
"require": "./generator-build/index.js",
"import": "./generator-build/index.js",
"default": "./generator-build/index.js"
},
"./sql": {
"require": {
"types": "./sql.d.ts",
"node": "./sql.js",
"default": "./sql.js"
},
"import": {
"types": "./sql.d.ts",
"node": "./sql.mjs",
"default": "./sql.mjs"
},
"default": "./sql.js"
},
"./*": "./*"
},
"version": "6.10.1",
"sideEffects": false
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+370
View File
@@ -0,0 +1,370 @@
declare class AnyNull extends NullTypesEnumValue {
#private;
}
declare type Args<T, F extends Operation> = T extends {
[K: symbol]: {
types: {
operations: {
[K in F]: {
args: any;
};
};
};
};
} ? T[symbol]['types']['operations'][F]['args'] : any;
declare class DbNull extends NullTypesEnumValue {
#private;
}
export declare function Decimal(n: Decimal.Value): Decimal;
export declare namespace Decimal {
export type Constructor = typeof Decimal;
export type Instance = Decimal;
export type Rounding = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
export type Modulo = Rounding | 9;
export type Value = string | number | Decimal;
// http://mikemcl.github.io/decimal.js/#constructor-properties
export interface Config {
precision?: number;
rounding?: Rounding;
toExpNeg?: number;
toExpPos?: number;
minE?: number;
maxE?: number;
crypto?: boolean;
modulo?: Modulo;
defaults?: boolean;
}
}
export declare class Decimal {
readonly d: number[];
readonly e: number;
readonly s: number;
constructor(n: Decimal.Value);
absoluteValue(): Decimal;
abs(): Decimal;
ceil(): Decimal;
clampedTo(min: Decimal.Value, max: Decimal.Value): Decimal;
clamp(min: Decimal.Value, max: Decimal.Value): Decimal;
comparedTo(n: Decimal.Value): number;
cmp(n: Decimal.Value): number;
cosine(): Decimal;
cos(): Decimal;
cubeRoot(): Decimal;
cbrt(): Decimal;
decimalPlaces(): number;
dp(): number;
dividedBy(n: Decimal.Value): Decimal;
div(n: Decimal.Value): Decimal;
dividedToIntegerBy(n: Decimal.Value): Decimal;
divToInt(n: Decimal.Value): Decimal;
equals(n: Decimal.Value): boolean;
eq(n: Decimal.Value): boolean;
floor(): Decimal;
greaterThan(n: Decimal.Value): boolean;
gt(n: Decimal.Value): boolean;
greaterThanOrEqualTo(n: Decimal.Value): boolean;
gte(n: Decimal.Value): boolean;
hyperbolicCosine(): Decimal;
cosh(): Decimal;
hyperbolicSine(): Decimal;
sinh(): Decimal;
hyperbolicTangent(): Decimal;
tanh(): Decimal;
inverseCosine(): Decimal;
acos(): Decimal;
inverseHyperbolicCosine(): Decimal;
acosh(): Decimal;
inverseHyperbolicSine(): Decimal;
asinh(): Decimal;
inverseHyperbolicTangent(): Decimal;
atanh(): Decimal;
inverseSine(): Decimal;
asin(): Decimal;
inverseTangent(): Decimal;
atan(): Decimal;
isFinite(): boolean;
isInteger(): boolean;
isInt(): boolean;
isNaN(): boolean;
isNegative(): boolean;
isNeg(): boolean;
isPositive(): boolean;
isPos(): boolean;
isZero(): boolean;
lessThan(n: Decimal.Value): boolean;
lt(n: Decimal.Value): boolean;
lessThanOrEqualTo(n: Decimal.Value): boolean;
lte(n: Decimal.Value): boolean;
logarithm(n?: Decimal.Value): Decimal;
log(n?: Decimal.Value): Decimal;
minus(n: Decimal.Value): Decimal;
sub(n: Decimal.Value): Decimal;
modulo(n: Decimal.Value): Decimal;
mod(n: Decimal.Value): Decimal;
naturalExponential(): Decimal;
exp(): Decimal;
naturalLogarithm(): Decimal;
ln(): Decimal;
negated(): Decimal;
neg(): Decimal;
plus(n: Decimal.Value): Decimal;
add(n: Decimal.Value): Decimal;
precision(includeZeros?: boolean): number;
sd(includeZeros?: boolean): number;
round(): Decimal;
sine() : Decimal;
sin() : Decimal;
squareRoot(): Decimal;
sqrt(): Decimal;
tangent() : Decimal;
tan() : Decimal;
times(n: Decimal.Value): Decimal;
mul(n: Decimal.Value) : Decimal;
toBinary(significantDigits?: number): string;
toBinary(significantDigits: number, rounding: Decimal.Rounding): string;
toDecimalPlaces(decimalPlaces?: number): Decimal;
toDecimalPlaces(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toDP(decimalPlaces?: number): Decimal;
toDP(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
toExponential(decimalPlaces?: number): string;
toExponential(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFixed(decimalPlaces?: number): string;
toFixed(decimalPlaces: number, rounding: Decimal.Rounding): string;
toFraction(max_denominator?: Decimal.Value): Decimal[];
toHexadecimal(significantDigits?: number): string;
toHexadecimal(significantDigits: number, rounding: Decimal.Rounding): string;
toHex(significantDigits?: number): string;
toHex(significantDigits: number, rounding?: Decimal.Rounding): string;
toJSON(): string;
toNearest(n: Decimal.Value, rounding?: Decimal.Rounding): Decimal;
toNumber(): number;
toOctal(significantDigits?: number): string;
toOctal(significantDigits: number, rounding: Decimal.Rounding): string;
toPower(n: Decimal.Value): Decimal;
pow(n: Decimal.Value): Decimal;
toPrecision(significantDigits?: number): string;
toPrecision(significantDigits: number, rounding: Decimal.Rounding): string;
toSignificantDigits(significantDigits?: number): Decimal;
toSignificantDigits(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toSD(significantDigits?: number): Decimal;
toSD(significantDigits: number, rounding: Decimal.Rounding): Decimal;
toString(): string;
truncated(): Decimal;
trunc(): Decimal;
valueOf(): string;
static abs(n: Decimal.Value): Decimal;
static acos(n: Decimal.Value): Decimal;
static acosh(n: Decimal.Value): Decimal;
static add(x: Decimal.Value, y: Decimal.Value): Decimal;
static asin(n: Decimal.Value): Decimal;
static asinh(n: Decimal.Value): Decimal;
static atan(n: Decimal.Value): Decimal;
static atanh(n: Decimal.Value): Decimal;
static atan2(y: Decimal.Value, x: Decimal.Value): Decimal;
static cbrt(n: Decimal.Value): Decimal;
static ceil(n: Decimal.Value): Decimal;
static clamp(n: Decimal.Value, min: Decimal.Value, max: Decimal.Value): Decimal;
static clone(object?: Decimal.Config): Decimal.Constructor;
static config(object: Decimal.Config): Decimal.Constructor;
static cos(n: Decimal.Value): Decimal;
static cosh(n: Decimal.Value): Decimal;
static div(x: Decimal.Value, y: Decimal.Value): Decimal;
static exp(n: Decimal.Value): Decimal;
static floor(n: Decimal.Value): Decimal;
static hypot(...n: Decimal.Value[]): Decimal;
static isDecimal(object: any): object is Decimal;
static ln(n: Decimal.Value): Decimal;
static log(n: Decimal.Value, base?: Decimal.Value): Decimal;
static log2(n: Decimal.Value): Decimal;
static log10(n: Decimal.Value): Decimal;
static max(...n: Decimal.Value[]): Decimal;
static min(...n: Decimal.Value[]): Decimal;
static mod(x: Decimal.Value, y: Decimal.Value): Decimal;
static mul(x: Decimal.Value, y: Decimal.Value): Decimal;
static noConflict(): Decimal.Constructor; // Browser only
static pow(base: Decimal.Value, exponent: Decimal.Value): Decimal;
static random(significantDigits?: number): Decimal;
static round(n: Decimal.Value): Decimal;
static set(object: Decimal.Config): Decimal.Constructor;
static sign(n: Decimal.Value): number;
static sin(n: Decimal.Value): Decimal;
static sinh(n: Decimal.Value): Decimal;
static sqrt(n: Decimal.Value): Decimal;
static sub(x: Decimal.Value, y: Decimal.Value): Decimal;
static sum(...n: Decimal.Value[]): Decimal;
static tan(n: Decimal.Value): Decimal;
static tanh(n: Decimal.Value): Decimal;
static trunc(n: Decimal.Value): Decimal;
static readonly default?: Decimal.Constructor;
static readonly Decimal?: Decimal.Constructor;
static readonly precision: number;
static readonly rounding: Decimal.Rounding;
static readonly toExpNeg: number;
static readonly toExpPos: number;
static readonly minE: number;
static readonly maxE: number;
static readonly crypto: boolean;
static readonly modulo: Decimal.Modulo;
static readonly ROUND_UP: 0;
static readonly ROUND_DOWN: 1;
static readonly ROUND_CEIL: 2;
static readonly ROUND_FLOOR: 3;
static readonly ROUND_HALF_UP: 4;
static readonly ROUND_HALF_DOWN: 5;
static readonly ROUND_HALF_EVEN: 6;
static readonly ROUND_HALF_CEIL: 7;
static readonly ROUND_HALF_FLOOR: 8;
static readonly EUCLID: 9;
}
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
[K in keyof A]: Exact<A[K], W[K]>;
} : W) : never) | (A extends Narrowable ? A : never);
export declare function getRuntime(): GetRuntimeOutput;
declare type GetRuntimeOutput = {
id: RuntimeName;
prettyName: string;
isEdge: boolean;
};
declare class JsonNull extends NullTypesEnumValue {
#private;
}
/**
* Generates more strict variant of an enum which, unlike regular enum,
* throws on non-existing property access. This can be useful in following situations:
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
* - enum values are generated dynamically from DMMF.
*
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
* will result in `undefined` value being used, which will be accepted. Using strict enum
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
*
* Note: if you need to check for existence of a value in the enum you can still use either
* `in` operator or `hasOwnProperty` function.
*
* @param definition
* @returns
*/
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
declare type Narrowable = string | number | bigint | boolean | [];
declare class NullTypesEnumValue extends ObjectEnumValue {
_getNamespace(): string;
}
/**
* Base class for unique values of object-valued enums.
*/
declare abstract class ObjectEnumValue {
constructor(arg?: symbol);
abstract _getNamespace(): string;
_getName(): string;
toString(): string;
}
export declare const objectEnumValues: {
classes: {
DbNull: typeof DbNull;
JsonNull: typeof JsonNull;
AnyNull: typeof AnyNull;
};
instances: {
DbNull: DbNull;
JsonNull: JsonNull;
AnyNull: AnyNull;
};
};
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
declare namespace Public {
export {
validator
}
}
export { Public }
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
export { }
File diff suppressed because one or more lines are too long
+3681
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More