cambios en la interfaz de organizaciones

This commit is contained in:
2026-02-23 12:40:30 -04:00
parent e149500735
commit 0efd5a11bd
12 changed files with 2427 additions and 265 deletions

View File

@@ -0,0 +1,10 @@
ALTER TABLE "training_surveys" ALTER COLUMN "commune_spokesperson_cedula" DROP DEFAULT;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "commune_spokesperson_cedula" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "commune_spokesperson_rif" DROP DEFAULT;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "commune_spokesperson_rif" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "commune_email" DROP DEFAULT;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "commune_email" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "communal_council_spokesperson_cedula" DROP DEFAULT;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "communal_council_spokesperson_cedula" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "communal_council_spokesperson_rif" DROP DEFAULT;--> statement-breakpoint
ALTER TABLE "training_surveys" ALTER COLUMN "communal_council_spokesperson_rif" DROP NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -134,6 +134,13 @@
"when": 1771855467870,
"tag": "0018_milky_prism",
"breakpoints": true
},
{
"idx": 19,
"version": "7",
"when": 1771858973096,
"tag": "0019_cuddly_cobalt_man",
"breakpoints": true
}
]
}

View File

@@ -262,4 +262,19 @@ export class CreateTrainingDto {
@ApiProperty()
@IsString()
parish: string;
@ApiProperty()
@IsString()
@IsOptional()
photo1?: string;
@ApiProperty()
@IsString()
@IsOptional()
photo2?: string;
@ApiProperty()
@IsString()
@IsOptional()
photo3?: string;
}

View File

@@ -313,27 +313,41 @@ export class TrainingService {
// Handle photo updates/removals
const photoFields = ['photo1', 'photo2', 'photo3'] as const;
// 1. If we have NEW files, they replace any old files or occupy empty slots
if (photoPaths.length > 0) {
photoPaths.forEach((newPath, idx) => {
const fieldName = photoFields[idx];
const oldPath = currentRecord[fieldName];
if (oldPath && oldPath !== newPath) {
this.deleteFile(oldPath);
}
updateData[fieldName] = newPath;
});
}
// 2. If the user explicitly cleared a photo field (updateData.photoX === '')
// 1. First, handle explicit deletions (where field is '')
photoFields.forEach((field) => {
if (updateData[field] === '') {
const oldPath = currentRecord[field];
if (oldPath) this.deleteFile(oldPath);
updateData[field] = null; // Set to null in DB
updateData[field] = null;
}
});
// 2. We need to find which slots are currently "available" (null) after deletions
// and which ones have existing URLs that we want to keep.
// Let's determine the final state of the 3 slots.
const finalPhotos: (string | null)[] = [
updateData.photo1 !== undefined ? updateData.photo1 : currentRecord.photo1,
updateData.photo2 !== undefined ? updateData.photo2 : currentRecord.photo2,
updateData.photo3 !== undefined ? updateData.photo3 : currentRecord.photo3,
];
// 3. Fill the available (null) slots with NEW photo paths
if (photoPaths.length > 0) {
let photoPathIdx = 0;
for (let i = 0; i < 3 && photoPathIdx < photoPaths.length; i++) {
if (!finalPhotos[i]) {
finalPhotos[i] = photoPaths[photoPathIdx];
photoPathIdx++;
}
}
}
// Assign back to updateData
updateData.photo1 = finalPhotos[0];
updateData.photo2 = finalPhotos[1];
updateData.photo3 = finalPhotos[2];
if (updateTrainingDto.visitDate) {
updateData.visitDate = new Date(updateTrainingDto.visitDate);
}

View File

@@ -45,7 +45,8 @@ import {
CardTitle,
} from '@repo/shadcn/components/ui/card';
import { SelectSearchable } from '@repo/shadcn/components/ui/select-searchable';
import React from 'react';
import React, { useEffect } from 'react';
import { toast } from 'sonner';
const OSP_TYPES = ['EPSIC', 'EPSDC', 'UPF', 'OTROS', 'COOPERATIVA'];
const STATUS_OPTIONS = ['ACTIVA', 'INACTIVA'];
@@ -160,6 +161,16 @@ export function CreateTrainingForm({
mode: 'onChange',
});
// 1. Extrae errors de formState
const { formState: { errors } } = form;
// 2. Crea un efecto para monitorearlos
useEffect(() => {
if (Object.keys(errors).length > 0) {
console.log("Campos con errores:", errors);
}
}, [errors]);
// Cascading Select Logic
const ecoSector = useWatch({ control: form.control, name: 'ecoSector' });
const productiveSector = useWatch({
@@ -253,6 +264,8 @@ export function CreateTrainingForm({
}, [defaultValues]);
const onSubmit = async (formData: TrainingSchema) => {
const data = new FormData();
// 1. Definimos las claves que NO queremos enviar en el bucle general
@@ -260,9 +273,6 @@ export function CreateTrainingForm({
// 'photo1/2/3' son strings (urls viejas) que no queremos reenviar como texto.
const excludedKeys = [
'files',
'photo1',
'photo2',
'photo3',
'coorState',
'coorMunicipality',
'coorParish',
@@ -1039,7 +1049,7 @@ export function CreateTrainingForm({
render={({ field }) => (
<FormItem className="w-full flex flex-col space-y-2">
<FormLabel className="font-semibold">
Correo Electrónico de la Comuna
Correo Electrónico de la Comuna (Opcional)
</FormLabel>
<FormControl>
<Input type="email" {...field} />
@@ -1141,7 +1151,7 @@ export function CreateTrainingForm({
render={({ field }) => (
<FormItem className="w-full flex flex-col space-y-2">
<FormLabel className="font-semibold">
Correo Electrónico del Consejo Comunal
Correo Electrónico del Consejo Comunal (Opcional)
</FormLabel>
<FormControl>
<Input type="email" {...field} />
@@ -1298,7 +1308,7 @@ export function CreateTrainingForm({
name="generalObservations"
render={({ field }) => (
<FormItem>
<FormLabel>Observaciones Generales</FormLabel>
<FormLabel>Observaciones Generales (Opcional)</FormLabel>
<FormControl>
<Textarea {...field} />
</FormControl>
@@ -1374,19 +1384,25 @@ export function CreateTrainingForm({
multiple
accept="image/*"
onChange={(e) => {
const files = Array.from(e.target.files || []);
const newFiles = Array.from(e.target.files || []);
const existingCount = [
form.watch('photo1'),
form.watch('photo2'),
form.watch('photo3'),
].filter(Boolean).length;
if (files.length + existingCount > 3) {
alert('Máximo 3 imágenes en total');
if (
newFiles.length + selectedFiles.length + existingCount >
3
) {
toast.error(`Máximo 3 imágenes en total. Ya tienes ${existingCount} subidas y ${selectedFiles.length} seleccionadas para subir.`)
e.target.value = '';
return;
}
setSelectedFiles(files);
setSelectedFiles((prev) => [...prev, ...newFiles]);
// Reset the input value so the same file can be selected again if removed
e.target.value = '';
}}
/>
</div>
@@ -1398,13 +1414,37 @@ export function CreateTrainingForm({
{selectedFiles.map((file, idx) => (
<div
key={idx}
className="relative aspect-square rounded-md overflow-hidden bg-muted"
className="relative aspect-square rounded-md overflow-hidden bg-muted group"
>
<img
src={URL.createObjectURL(file)}
alt={`Preview ${idx + 1}`}
className="object-cover w-full h-full"
/>
<button
type="button"
onClick={() => {
setSelectedFiles((prev) =>
prev.filter((_, i) => i !== idx),
);
}}
className="absolute top-1 right-1 bg-destructive text-destructive-foreground rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
))}
</div>

View File

@@ -35,6 +35,17 @@ import { Trash2 } from 'lucide-react';
import { useState } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
const UNIT_OPTIONS = [
'KG',
'TON',
'UNID',
'LT',
'MTS',
'QQ',
'HM2',
'SACOS',
];
// 1. Definimos la estructura de los datos para que TypeScript no se queje
interface ProductItem {
productName: string;
@@ -68,20 +79,21 @@ export function ProductActivityList() {
dailyCount: '',
weeklyCount: '',
monthlyCount: '',
internalDistributionZone: '',
// Internal dist
internalState: undefined,
internalMunicipality: undefined,
internalParish: undefined,
internalState: null,
internalMunicipality: null,
internalParish: null,
internalDescription: '',
internalQuantity: '',
internalUnit: '',
// External dist
externalCountry: '',
externalState: undefined,
externalMunicipality: undefined,
externalParish: undefined,
externalState: null,
externalMunicipality: null,
externalParish: null,
externalCity: '',
externalDescription: '',
externalQuantity: '',
@@ -117,16 +129,17 @@ export function ProductActivityList() {
dailyCount: '',
weeklyCount: '',
monthlyCount: '',
internalState: undefined,
internalMunicipality: undefined,
internalParish: undefined,
internalDistributionZone: '',
internalState: null,
internalMunicipality: null,
internalParish: null,
internalDescription: '',
internalQuantity: '',
internalUnit: '',
externalCountry: '',
externalState: undefined,
externalMunicipality: undefined,
externalParish: undefined,
externalState: null,
externalMunicipality: null,
externalParish: null,
externalCity: '',
externalDescription: '',
externalQuantity: '',
@@ -337,9 +350,11 @@ export function ProductActivityList() {
/>
</div>
<div className="space-y-2">
<Label>Cantidad Numérica (KG, TON, UNID. LT, MTS,QQ, HM2, SACOS)</Label>
<Label>Cantidad Numérica</Label>
<div className="flex gap-2">
<Input
type="number"
className="flex-1"
value={newItem.externalQuantity}
onChange={(e) =>
setNewItem({
@@ -348,6 +363,24 @@ export function ProductActivityList() {
})
}
/>
<Select
value={newItem.externalUnit}
onValueChange={(val) =>
setNewItem({ ...newItem, externalUnit: val })
}
>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="Unidad" />
</SelectTrigger>
<SelectContent>
{UNIT_OPTIONS.map((unit) => (
<SelectItem key={unit} value={unit}>
{unit}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>

View File

@@ -17,10 +17,28 @@ import {
} from '@repo/shadcn/components/ui/table';
import { Input } from '@repo/shadcn/input';
import { Label } from '@repo/shadcn/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@repo/shadcn/select';
import { Trash2 } from 'lucide-react';
import { useState } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
const UNIT_OPTIONS = [
'KG',
'TON',
'UNID',
'LT',
'MTS',
'QQ',
'HM2',
'SACOS',
];
export function ProductionList() {
const { control, register } = useFormContext();
const { fields, append, remove } = useFieldArray({
@@ -32,12 +50,13 @@ export function ProductionList() {
rawMaterial: '',
supplyType: '',
quantity: '',
unit: '',
});
const handleAdd = () => {
if (newItem.rawMaterial && newItem.quantity) {
append({ ...newItem, quantity: Number(newItem.quantity) });
setNewItem({ rawMaterial: '', supplyType: '', quantity: '' });
setNewItem({ rawMaterial: '', supplyType: '', quantity: '', unit: '' });
setIsOpen(false);
}
};
@@ -79,15 +98,35 @@ export function ProductionList() {
/>
</div>
<div className="space-y-2">
<Label>Cantidad Mensual (Kg, TON, UNID.LT, MTS,QQ,HM2,SACO)</Label>
<Label>Cantidad Mensual</Label>
<div className="flex gap-2">
<Input
type="number"
className="flex-1"
value={newItem.quantity}
onChange={(e) =>
setNewItem({ ...newItem, quantity: e.target.value })
}
placeholder="0"
/>
<Select
value={newItem.unit}
onValueChange={(val) =>
setNewItem({ ...newItem, unit: val })
}
>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="Unidad" />
</SelectTrigger>
<SelectContent>
{UNIT_OPTIONS.map((unit) => (
<SelectItem key={unit} value={unit}>
{unit}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="flex justify-end gap-4">
<Button
@@ -139,8 +178,12 @@ export function ProductionList() {
type="hidden"
{...register(`productionList.${index}.quantity`)}
/>
<input
type="hidden"
{...register(`productionList.${index}.unit`)}
/>
{/* @ts-ignore */}
{field.quantity}
{field.quantity} {field.unit}
</TableCell>
<TableCell>
<Button

View File

@@ -40,11 +40,29 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
}
};
const handleExport = (id?: number | undefined) => {
window.open(`${apiUrl}/training/export/${id}`, '_blank');
};
// Mapear roles a minúsculas para comparación segura
const userRoles = session?.user?.role?.map((r) => r.rol.toLowerCase()) || [];
const role = session?.user.role[0]?.rol;
const isAdminOrSuper = userRoles.some((r) =>
['superadmin', 'admin'].includes(r),
);
// Soporta tanto 'coordinator' como 'coordinador'
const isCoordinator = userRoles.some(r =>
r.includes('coordinator') || r.includes('coordinador')
);
const isOtherAuthorized = userRoles.some((r) =>
['autoridad', 'manager'].includes(r),
);
// El creador del registro: intentamos createdBy o created_by por si acaso
const createdBy = data.createdBy ?? (data as any).created_by;
// Comparación robusta de IDs
const isOwner = createdBy !== undefined &&
createdBy !== null &&
Number(createdBy) === Number(session?.user?.id);
return (
<>
@@ -64,10 +82,8 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
/>
<div className="flex gap-1">
{/* VER DETALLE: superadmin, admin, autoridad, manager */}
{['superadmin', 'admin', 'autoridad', 'manager'].includes(
role ?? '',
) && (
{/* VER DETALLE: superadmin, admin, autoridad, manager, or owner coordinator */}
{(isAdminOrSuper || isOtherAuthorized || (isCoordinator && isOwner)) && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
@@ -86,10 +102,8 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
</TooltipProvider>
)}
{/* EDITAR Y ELIMINAR: Solo superadmin y admin */}
{['superadmin', 'admin'].includes(role ?? '') && (
<>
{/* Editar */}
{/* EDITAR: Superadmin, admin OR (coordinator if owner) */}
{(isAdminOrSuper || (isCoordinator && isOwner)) && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
@@ -108,8 +122,10 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{/* Eliminar */}
{/* ELIMINAR: Solo superadmin y admin */}
{isAdminOrSuper && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
@@ -126,7 +142,6 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
</TooltipContent>
</Tooltip>
</TooltipProvider>
</>
)}
</div>
</>

View File

@@ -273,7 +273,7 @@ export function TrainingViewModal({
<span className="text-xs font-bold text-muted-foreground block mb-1">
DISTRIBUCIÓN INTERNA
</span>
<p>Cant: {prod.internalQuantity}</p>
<p>Cant: {prod.internalQuantity} {prod.internalUnit}</p>
<p className="text-xs text-muted-foreground">
{prod.internalDescription}
</p>
@@ -284,7 +284,7 @@ export function TrainingViewModal({
<span className="text-xs font-bold text-muted-foreground block mb-1">
EXPORTACIÓN ({prod.externalCountry})
</span>
<p>Cant: {prod.externalQuantity}</p>
<p>Cant: {prod.externalQuantity} {prod.externalUnit}</p>
<p className="text-xs text-muted-foreground">
{prod.externalDescription}
</p>
@@ -360,7 +360,7 @@ export function TrainingViewModal({
{mat.supplyType}
</p>
</div>
<Badge variant="secondary">Cant: {mat.quantity}</Badge>
<Badge variant="secondary">Cant: {mat.quantity} {mat.unit}</Badge>
</div>
))}
{(!data.productionList ||

View File

@@ -11,15 +11,18 @@ const productItemSchema = z.object({
// Distribución Interna
internalDistributionZone: z.string().optional(),
internalQuantity: z.coerce.string().or(z.number()).optional(),
internalUnit: z.string().optional(),
// Distribución Externa
externalCountry: z.string().optional(),
externalState: z.number().optional(),
externalMunicipality: z.number().optional(),
externalParish: z.number().optional(),
externalState: z.number().optional().nullable(),
externalMunicipality: z.number().optional().nullable(),
externalParish: z.number().optional().nullable(),
externalCity: z.string().optional(),
externalDescription: z.string().optional(),
externalQuantity: z.coerce.string().or(z.number()).optional(),
externalUnit: z.string().optional(),
// Mano de obra
womenCount: z.coerce.string().or(z.number()).optional(),
@@ -30,6 +33,7 @@ const productionItemSchema = z.object({
rawMaterial: z.string(),
supplyType: z.string().optional(),
quantity: z.coerce.string().or(z.number()).optional(), // Aceptamos string o number por los inputs
unit: z.string().optional(),
});
const equipmentItemSchema = z.object({
@@ -152,6 +156,7 @@ export const trainingSchema = z.object({
photo1: z.string().optional().nullable(),
photo2: z.string().optional().nullable(),
photo3: z.string().optional().nullable(),
createdBy: z.number().optional().nullable(),
});
export type TrainingSchema = z.infer<typeof trainingSchema>;

View File

@@ -8,111 +8,111 @@
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(0.9824 0.0013 286.3757);
--foreground: oklch(0.3211 0 0);
--background: oklch(0.9751 0.0127 244.2507);
--foreground: oklch(0.3729 0.0306 259.7328);
--card: oklch(1.0000 0 0);
--card-foreground: oklch(0.3211 0 0);
--card-foreground: oklch(0.3729 0.0306 259.7328);
--popover: oklch(1.0000 0 0);
--popover-foreground: oklch(0.3211 0 0);
--primary: oklch(0.6487 0.1538 150.3071);
--popover-foreground: oklch(0.3729 0.0306 259.7328);
--primary: oklch(0.7227 0.1920 149.5793);
--primary-foreground: oklch(1.0000 0 0);
--secondary: oklch(0.6746 0.1414 261.3380);
--secondary-foreground: oklch(1.0000 0 0);
--muted: oklch(0.8828 0.0285 98.1033);
--muted-foreground: oklch(0.5382 0 0);
--accent: oklch(0.8269 0.1080 211.9627);
--accent-foreground: oklch(0.3211 0 0);
--secondary: oklch(0.9514 0.0250 236.8242);
--secondary-foreground: oklch(0.4461 0.0263 256.8018);
--muted: oklch(0.9670 0.0029 264.5419);
--muted-foreground: oklch(0.5510 0.0234 264.3637);
--accent: oklch(0.9505 0.0507 163.0508);
--accent-foreground: oklch(0.3729 0.0306 259.7328);
--destructive: oklch(0.6368 0.2078 25.3313);
--destructive-foreground: oklch(1.0000 0 0);
--border: oklch(0.8699 0 0);
--input: oklch(0.8699 0 0);
--ring: oklch(0.6487 0.1538 150.3071);
--chart-1: oklch(0.6487 0.1538 150.3071);
--chart-2: oklch(0.6746 0.1414 261.3380);
--chart-3: oklch(0.8269 0.1080 211.9627);
--chart-4: oklch(0.5880 0.0993 245.7394);
--chart-5: oklch(0.5905 0.1608 148.2409);
--sidebar: oklch(0.9824 0.0013 286.3757);
--sidebar-foreground: oklch(0.3211 0 0);
--sidebar-primary: oklch(0.6487 0.1538 150.3071);
--border: oklch(0.9276 0.0058 264.5313);
--input: oklch(0.9276 0.0058 264.5313);
--ring: oklch(0.7227 0.1920 149.5793);
--chart-1: oklch(0.7227 0.1920 149.5793);
--chart-2: oklch(0.6959 0.1491 162.4796);
--chart-3: oklch(0.5960 0.1274 163.2254);
--chart-4: oklch(0.5081 0.1049 165.6121);
--chart-5: oklch(0.4318 0.0865 166.9128);
--sidebar: oklch(0.9514 0.0250 236.8242);
--sidebar-foreground: oklch(0.3729 0.0306 259.7328);
--sidebar-primary: oklch(0.7227 0.1920 149.5793);
--sidebar-primary-foreground: oklch(1.0000 0 0);
--sidebar-accent: oklch(0.8269 0.1080 211.9627);
--sidebar-accent-foreground: oklch(0.3211 0 0);
--sidebar-border: oklch(0.8699 0 0);
--sidebar-ring: oklch(0.6487 0.1538 150.3071);
--font-sans: Plus Jakarta Sans, sans-serif;
--font-serif: Source Serif 4, serif;
--font-mono: JetBrains Mono, monospace;
--sidebar-accent: oklch(0.9505 0.0507 163.0508);
--sidebar-accent-foreground: oklch(0.3729 0.0306 259.7328);
--sidebar-border: oklch(0.9276 0.0058 264.5313);
--sidebar-ring: oklch(0.7227 0.1920 149.5793);
--font-sans: DM Sans, sans-serif;
--font-serif: Lora, serif;
--font-mono: IBM Plex Mono, monospace;
--radius: 0.5rem;
--shadow-x: 0;
--shadow-y: 1px;
--shadow-blur: 3px;
--shadow-spread: 0px;
--shadow-x: 0px;
--shadow-y: 4px;
--shadow-blur: 8px;
--shadow-spread: -1px;
--shadow-opacity: 0.1;
--shadow-color: oklch(0 0 0);
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
--shadow-color: hsl(0 0% 0%);
--shadow-2xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05);
--shadow-xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05);
--shadow-sm: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10);
--shadow: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10);
--shadow-md: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10);
--shadow-lg: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10);
--shadow-xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10);
--shadow-2xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.25);
--tracking-normal: 0em;
--spacing: 0.25rem;
}
.dark {
--background: oklch(0.2303 0.0125 264.2926);
--foreground: oklch(0.9219 0 0);
--card: oklch(0.3210 0.0078 223.6661);
--card-foreground: oklch(0.9219 0 0);
--popover: oklch(0.3210 0.0078 223.6661);
--popover-foreground: oklch(0.9219 0 0);
--primary: oklch(0.6487 0.1538 150.3071);
--primary-foreground: oklch(1.0000 0 0);
--secondary: oklch(0.5880 0.0993 245.7394);
--secondary-foreground: oklch(0.9219 0 0);
--muted: oklch(0.3867 0 0);
--muted-foreground: oklch(0.7155 0 0);
--accent: oklch(0.6746 0.1414 261.3380);
--accent-foreground: oklch(0.9219 0 0);
--background: oklch(0.2077 0.0398 265.7549);
--foreground: oklch(0.8717 0.0093 258.3382);
--card: oklch(0.2795 0.0368 260.0310);
--card-foreground: oklch(0.8717 0.0093 258.3382);
--popover: oklch(0.2795 0.0368 260.0310);
--popover-foreground: oklch(0.8717 0.0093 258.3382);
--primary: oklch(0.7729 0.1535 163.2231);
--primary-foreground: oklch(0.2077 0.0398 265.7549);
--secondary: oklch(0.3351 0.0331 260.9120);
--secondary-foreground: oklch(0.7118 0.0129 286.0665);
--muted: oklch(0.2463 0.0275 259.9628);
--muted-foreground: oklch(0.5510 0.0234 264.3637);
--accent: oklch(0.3729 0.0306 259.7328);
--accent-foreground: oklch(0.7118 0.0129 286.0665);
--destructive: oklch(0.6368 0.2078 25.3313);
--destructive-foreground: oklch(1.0000 0 0);
--border: oklch(0.3867 0 0);
--input: oklch(0.3867 0 0);
--ring: oklch(0.6487 0.1538 150.3071);
--chart-1: oklch(0.6487 0.1538 150.3071);
--chart-2: oklch(0.5880 0.0993 245.7394);
--chart-3: oklch(0.6746 0.1414 261.3380);
--chart-4: oklch(0.8269 0.1080 211.9627);
--chart-5: oklch(0.5905 0.1608 148.2409);
--sidebar: oklch(0.2303 0.0125 264.2926);
--sidebar-foreground: oklch(0.9219 0 0);
--sidebar-primary: oklch(0.6487 0.1538 150.3071);
--sidebar-primary-foreground: oklch(1.0000 0 0);
--sidebar-accent: oklch(0.6746 0.1414 261.3380);
--sidebar-accent-foreground: oklch(0.9219 0 0);
--sidebar-border: oklch(0.3867 0 0);
--sidebar-ring: oklch(0.6487 0.1538 150.3071);
--font-sans: Plus Jakarta Sans, sans-serif;
--font-serif: Source Serif 4, serif;
--font-mono: JetBrains Mono, monospace;
--destructive-foreground: oklch(0.2077 0.0398 265.7549);
--border: oklch(0.4461 0.0263 256.8018);
--input: oklch(0.4461 0.0263 256.8018);
--ring: oklch(0.7729 0.1535 163.2231);
--chart-1: oklch(0.7729 0.1535 163.2231);
--chart-2: oklch(0.7845 0.1325 181.9120);
--chart-3: oklch(0.7227 0.1920 149.5793);
--chart-4: oklch(0.6959 0.1491 162.4796);
--chart-5: oklch(0.5960 0.1274 163.2254);
--sidebar: oklch(0.2795 0.0368 260.0310);
--sidebar-foreground: oklch(0.8717 0.0093 258.3382);
--sidebar-primary: oklch(0.7729 0.1535 163.2231);
--sidebar-primary-foreground: oklch(0.2077 0.0398 265.7549);
--sidebar-accent: oklch(0.3729 0.0306 259.7328);
--sidebar-accent-foreground: oklch(0.7118 0.0129 286.0665);
--sidebar-border: oklch(0.4461 0.0263 256.8018);
--sidebar-ring: oklch(0.7729 0.1535 163.2231);
--font-sans: DM Sans, sans-serif;
--font-serif: Lora, serif;
--font-mono: IBM Plex Mono, monospace;
--radius: 0.5rem;
--shadow-x: 0;
--shadow-y: 1px;
--shadow-blur: 3px;
--shadow-spread: 0px;
--shadow-x: 0px;
--shadow-y: 4px;
--shadow-blur: 8px;
--shadow-spread: -1px;
--shadow-opacity: 0.1;
--shadow-color: oklch(0 0 0);
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
--shadow-color: hsl(0 0% 0%);
--shadow-2xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05);
--shadow-xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05);
--shadow-sm: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10);
--shadow: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10);
--shadow-md: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10);
--shadow-lg: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10);
--shadow-xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10);
--shadow-2xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.25);
}
@theme inline {
@@ -166,73 +166,12 @@
--shadow-lg: var(--shadow-lg);
--shadow-xl: var(--shadow-xl);
--shadow-2xl: var(--shadow-2xl);
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground overscroll-none;
/* font-feature-settings: "rlig" 1, "calt" 1; */
font-synthesis-weight: none;
text-rendering: optimizeLegibility;
}
@supports (font: -apple-system-body) and (-webkit-appearance: none) {
[data-wrapper] {
@apply min-[1800px]:border-t;
}
}
/* Custom scrollbar styling. Thanks @pranathiperii. */
::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
border-radius: 5px;
background: hsl(var(--border));
}
* {
scrollbar-color: hsl(var(--border)) transparent;
scrollbar-width: thin;
}
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}