cambios en la interfaz de organizaciones
This commit is contained in:
10
apps/api/src/database/migrations/0019_cuddly_cobalt_man.sql
Normal file
10
apps/api/src/database/migrations/0019_cuddly_cobalt_man.sql
Normal 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;
|
||||||
2041
apps/api/src/database/migrations/meta/0019_snapshot.json
Normal file
2041
apps/api/src/database/migrations/meta/0019_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -134,6 +134,13 @@
|
|||||||
"when": 1771855467870,
|
"when": 1771855467870,
|
||||||
"tag": "0018_milky_prism",
|
"tag": "0018_milky_prism",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 19,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1771858973096,
|
||||||
|
"tag": "0019_cuddly_cobalt_man",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -262,4 +262,19 @@ export class CreateTrainingDto {
|
|||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@IsString()
|
@IsString()
|
||||||
parish: string;
|
parish: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
photo1?: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
photo2?: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
photo3?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,27 +313,41 @@ export class TrainingService {
|
|||||||
// Handle photo updates/removals
|
// Handle photo updates/removals
|
||||||
const photoFields = ['photo1', 'photo2', 'photo3'] as const;
|
const photoFields = ['photo1', 'photo2', 'photo3'] as const;
|
||||||
|
|
||||||
// 1. If we have NEW files, they replace any old files or occupy empty slots
|
// 1. First, handle explicit deletions (where field is '')
|
||||||
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 === '')
|
|
||||||
photoFields.forEach((field) => {
|
photoFields.forEach((field) => {
|
||||||
if (updateData[field] === '') {
|
if (updateData[field] === '') {
|
||||||
const oldPath = currentRecord[field];
|
const oldPath = currentRecord[field];
|
||||||
if (oldPath) this.deleteFile(oldPath);
|
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) {
|
if (updateTrainingDto.visitDate) {
|
||||||
updateData.visitDate = new Date(updateTrainingDto.visitDate);
|
updateData.visitDate = new Date(updateTrainingDto.visitDate);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@repo/shadcn/components/ui/card';
|
} from '@repo/shadcn/components/ui/card';
|
||||||
import { SelectSearchable } from '@repo/shadcn/components/ui/select-searchable';
|
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 OSP_TYPES = ['EPSIC', 'EPSDC', 'UPF', 'OTROS', 'COOPERATIVA'];
|
||||||
const STATUS_OPTIONS = ['ACTIVA', 'INACTIVA'];
|
const STATUS_OPTIONS = ['ACTIVA', 'INACTIVA'];
|
||||||
@@ -160,6 +161,16 @@ export function CreateTrainingForm({
|
|||||||
mode: 'onChange',
|
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
|
// Cascading Select Logic
|
||||||
const ecoSector = useWatch({ control: form.control, name: 'ecoSector' });
|
const ecoSector = useWatch({ control: form.control, name: 'ecoSector' });
|
||||||
const productiveSector = useWatch({
|
const productiveSector = useWatch({
|
||||||
@@ -253,6 +264,8 @@ export function CreateTrainingForm({
|
|||||||
}, [defaultValues]);
|
}, [defaultValues]);
|
||||||
|
|
||||||
const onSubmit = async (formData: TrainingSchema) => {
|
const onSubmit = async (formData: TrainingSchema) => {
|
||||||
|
|
||||||
|
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
|
|
||||||
// 1. Definimos las claves que NO queremos enviar en el bucle general
|
// 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.
|
// 'photo1/2/3' son strings (urls viejas) que no queremos reenviar como texto.
|
||||||
const excludedKeys = [
|
const excludedKeys = [
|
||||||
'files',
|
'files',
|
||||||
'photo1',
|
|
||||||
'photo2',
|
|
||||||
'photo3',
|
|
||||||
'coorState',
|
'coorState',
|
||||||
'coorMunicipality',
|
'coorMunicipality',
|
||||||
'coorParish',
|
'coorParish',
|
||||||
@@ -1039,7 +1049,7 @@ export function CreateTrainingForm({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="w-full flex flex-col space-y-2">
|
<FormItem className="w-full flex flex-col space-y-2">
|
||||||
<FormLabel className="font-semibold">
|
<FormLabel className="font-semibold">
|
||||||
Correo Electrónico de la Comuna
|
Correo Electrónico de la Comuna (Opcional)
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input type="email" {...field} />
|
<Input type="email" {...field} />
|
||||||
@@ -1141,7 +1151,7 @@ export function CreateTrainingForm({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="w-full flex flex-col space-y-2">
|
<FormItem className="w-full flex flex-col space-y-2">
|
||||||
<FormLabel className="font-semibold">
|
<FormLabel className="font-semibold">
|
||||||
Correo Electrónico del Consejo Comunal
|
Correo Electrónico del Consejo Comunal (Opcional)
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input type="email" {...field} />
|
<Input type="email" {...field} />
|
||||||
@@ -1298,7 +1308,7 @@ export function CreateTrainingForm({
|
|||||||
name="generalObservations"
|
name="generalObservations"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Observaciones Generales</FormLabel>
|
<FormLabel>Observaciones Generales (Opcional)</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea {...field} />
|
<Textarea {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -1374,19 +1384,25 @@ export function CreateTrainingForm({
|
|||||||
multiple
|
multiple
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const files = Array.from(e.target.files || []);
|
const newFiles = Array.from(e.target.files || []);
|
||||||
const existingCount = [
|
const existingCount = [
|
||||||
form.watch('photo1'),
|
form.watch('photo1'),
|
||||||
form.watch('photo2'),
|
form.watch('photo2'),
|
||||||
form.watch('photo3'),
|
form.watch('photo3'),
|
||||||
].filter(Boolean).length;
|
].filter(Boolean).length;
|
||||||
|
|
||||||
if (files.length + existingCount > 3) {
|
if (
|
||||||
alert('Máximo 3 imágenes en total');
|
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 = '';
|
e.target.value = '';
|
||||||
return;
|
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>
|
</div>
|
||||||
@@ -1398,13 +1414,37 @@ export function CreateTrainingForm({
|
|||||||
{selectedFiles.map((file, idx) => (
|
{selectedFiles.map((file, idx) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className="relative aspect-square rounded-md overflow-hidden bg-muted"
|
className="relative aspect-square rounded-md overflow-hidden bg-muted group"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={URL.createObjectURL(file)}
|
src={URL.createObjectURL(file)}
|
||||||
alt={`Preview ${idx + 1}`}
|
alt={`Preview ${idx + 1}`}
|
||||||
className="object-cover w-full h-full"
|
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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,6 +35,17 @@ import { Trash2 } from 'lucide-react';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
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
|
// 1. Definimos la estructura de los datos para que TypeScript no se queje
|
||||||
interface ProductItem {
|
interface ProductItem {
|
||||||
productName: string;
|
productName: string;
|
||||||
@@ -68,20 +79,21 @@ export function ProductActivityList() {
|
|||||||
dailyCount: '',
|
dailyCount: '',
|
||||||
weeklyCount: '',
|
weeklyCount: '',
|
||||||
monthlyCount: '',
|
monthlyCount: '',
|
||||||
|
internalDistributionZone: '',
|
||||||
|
|
||||||
// Internal dist
|
// Internal dist
|
||||||
internalState: undefined,
|
internalState: null,
|
||||||
internalMunicipality: undefined,
|
internalMunicipality: null,
|
||||||
internalParish: undefined,
|
internalParish: null,
|
||||||
internalDescription: '',
|
internalDescription: '',
|
||||||
internalQuantity: '',
|
internalQuantity: '',
|
||||||
internalUnit: '',
|
internalUnit: '',
|
||||||
|
|
||||||
// External dist
|
// External dist
|
||||||
externalCountry: '',
|
externalCountry: '',
|
||||||
externalState: undefined,
|
externalState: null,
|
||||||
externalMunicipality: undefined,
|
externalMunicipality: null,
|
||||||
externalParish: undefined,
|
externalParish: null,
|
||||||
externalCity: '',
|
externalCity: '',
|
||||||
externalDescription: '',
|
externalDescription: '',
|
||||||
externalQuantity: '',
|
externalQuantity: '',
|
||||||
@@ -117,16 +129,17 @@ export function ProductActivityList() {
|
|||||||
dailyCount: '',
|
dailyCount: '',
|
||||||
weeklyCount: '',
|
weeklyCount: '',
|
||||||
monthlyCount: '',
|
monthlyCount: '',
|
||||||
internalState: undefined,
|
internalDistributionZone: '',
|
||||||
internalMunicipality: undefined,
|
internalState: null,
|
||||||
internalParish: undefined,
|
internalMunicipality: null,
|
||||||
|
internalParish: null,
|
||||||
internalDescription: '',
|
internalDescription: '',
|
||||||
internalQuantity: '',
|
internalQuantity: '',
|
||||||
internalUnit: '',
|
internalUnit: '',
|
||||||
externalCountry: '',
|
externalCountry: '',
|
||||||
externalState: undefined,
|
externalState: null,
|
||||||
externalMunicipality: undefined,
|
externalMunicipality: null,
|
||||||
externalParish: undefined,
|
externalParish: null,
|
||||||
externalCity: '',
|
externalCity: '',
|
||||||
externalDescription: '',
|
externalDescription: '',
|
||||||
externalQuantity: '',
|
externalQuantity: '',
|
||||||
@@ -337,17 +350,37 @@ export function ProductActivityList() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Cantidad Numérica (KG, TON, UNID. LT, MTS,QQ, HM2, SACOS)</Label>
|
<Label>Cantidad Numérica</Label>
|
||||||
<Input
|
<div className="flex gap-2">
|
||||||
type="number"
|
<Input
|
||||||
value={newItem.externalQuantity}
|
type="number"
|
||||||
onChange={(e) =>
|
className="flex-1"
|
||||||
setNewItem({
|
value={newItem.externalQuantity}
|
||||||
...newItem,
|
onChange={(e) =>
|
||||||
externalQuantity: e.target.value,
|
setNewItem({
|
||||||
})
|
...newItem,
|
||||||
}
|
externalQuantity: e.target.value,
|
||||||
/>
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,28 @@ import {
|
|||||||
} from '@repo/shadcn/components/ui/table';
|
} from '@repo/shadcn/components/ui/table';
|
||||||
import { Input } from '@repo/shadcn/input';
|
import { Input } from '@repo/shadcn/input';
|
||||||
import { Label } from '@repo/shadcn/label';
|
import { Label } from '@repo/shadcn/label';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@repo/shadcn/select';
|
||||||
import { Trash2 } from 'lucide-react';
|
import { Trash2 } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
|
const UNIT_OPTIONS = [
|
||||||
|
'KG',
|
||||||
|
'TON',
|
||||||
|
'UNID',
|
||||||
|
'LT',
|
||||||
|
'MTS',
|
||||||
|
'QQ',
|
||||||
|
'HM2',
|
||||||
|
'SACOS',
|
||||||
|
];
|
||||||
|
|
||||||
export function ProductionList() {
|
export function ProductionList() {
|
||||||
const { control, register } = useFormContext();
|
const { control, register } = useFormContext();
|
||||||
const { fields, append, remove } = useFieldArray({
|
const { fields, append, remove } = useFieldArray({
|
||||||
@@ -32,12 +50,13 @@ export function ProductionList() {
|
|||||||
rawMaterial: '',
|
rawMaterial: '',
|
||||||
supplyType: '',
|
supplyType: '',
|
||||||
quantity: '',
|
quantity: '',
|
||||||
|
unit: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
if (newItem.rawMaterial && newItem.quantity) {
|
if (newItem.rawMaterial && newItem.quantity) {
|
||||||
append({ ...newItem, quantity: Number(newItem.quantity) });
|
append({ ...newItem, quantity: Number(newItem.quantity) });
|
||||||
setNewItem({ rawMaterial: '', supplyType: '', quantity: '' });
|
setNewItem({ rawMaterial: '', supplyType: '', quantity: '', unit: '' });
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -79,15 +98,35 @@ export function ProductionList() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Cantidad Mensual (Kg, TON, UNID.LT, MTS,QQ,HM2,SACO)</Label>
|
<Label>Cantidad Mensual</Label>
|
||||||
<Input
|
<div className="flex gap-2">
|
||||||
type="number"
|
<Input
|
||||||
value={newItem.quantity}
|
type="number"
|
||||||
onChange={(e) =>
|
className="flex-1"
|
||||||
setNewItem({ ...newItem, quantity: e.target.value })
|
value={newItem.quantity}
|
||||||
}
|
onChange={(e) =>
|
||||||
placeholder="0"
|
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>
|
||||||
<div className="flex justify-end gap-4">
|
<div className="flex justify-end gap-4">
|
||||||
<Button
|
<Button
|
||||||
@@ -134,13 +173,17 @@ export function ProductionList() {
|
|||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
{field.supplyType}
|
{field.supplyType}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
{...register(`productionList.${index}.quantity`)}
|
{...register(`productionList.${index}.quantity`)}
|
||||||
/>
|
/>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
{...register(`productionList.${index}.unit`)}
|
||||||
|
/>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
{field.quantity}
|
{field.quantity} {field.unit}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -40,11 +40,29 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExport = (id?: number | undefined) => {
|
// Mapear roles a minúsculas para comparación segura
|
||||||
window.open(`${apiUrl}/training/export/${id}`, '_blank');
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -64,10 +82,8 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
{/* VER DETALLE: superadmin, admin, autoridad, manager */}
|
{/* VER DETALLE: superadmin, admin, autoridad, manager, or owner coordinator */}
|
||||||
{['superadmin', 'admin', 'autoridad', 'manager'].includes(
|
{(isAdminOrSuper || isOtherAuthorized || (isCoordinator && isOwner)) && (
|
||||||
role ?? '',
|
|
||||||
) && (
|
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@@ -86,47 +102,46 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* EDITAR Y ELIMINAR: Solo superadmin y admin */}
|
{/* EDITAR: Superadmin, admin OR (coordinator if owner) */}
|
||||||
{['superadmin', 'admin'].includes(role ?? '') && (
|
{(isAdminOrSuper || (isCoordinator && isOwner)) && (
|
||||||
<>
|
<TooltipProvider>
|
||||||
{/* Editar */}
|
<Tooltip>
|
||||||
<TooltipProvider>
|
<TooltipTrigger asChild>
|
||||||
<Tooltip>
|
<Button
|
||||||
<TooltipTrigger asChild>
|
variant="outline"
|
||||||
<Button
|
size="icon"
|
||||||
variant="outline"
|
onClick={() =>
|
||||||
size="icon"
|
router.push(`/dashboard/formulario/editar/${data.id}`)
|
||||||
onClick={() =>
|
}
|
||||||
router.push(`/dashboard/formulario/editar/${data.id}`)
|
>
|
||||||
}
|
<Edit className="h-4 w-4" />
|
||||||
>
|
</Button>
|
||||||
<Edit className="h-4 w-4" />
|
</TooltipTrigger>
|
||||||
</Button>
|
<TooltipContent>
|
||||||
</TooltipTrigger>
|
<p>Editar</p>
|
||||||
<TooltipContent>
|
</TooltipContent>
|
||||||
<p>Editar</p>
|
</Tooltip>
|
||||||
</TooltipContent>
|
</TooltipProvider>
|
||||||
</Tooltip>
|
)}
|
||||||
</TooltipProvider>
|
|
||||||
|
|
||||||
{/* Eliminar */}
|
{/* ELIMINAR: Solo superadmin y admin */}
|
||||||
<TooltipProvider>
|
{isAdminOrSuper && (
|
||||||
<Tooltip>
|
<TooltipProvider>
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<Button
|
<TooltipTrigger asChild>
|
||||||
variant="outline"
|
<Button
|
||||||
size="icon"
|
variant="outline"
|
||||||
onClick={() => setOpen(true)}
|
size="icon"
|
||||||
>
|
onClick={() => setOpen(true)}
|
||||||
<Trash className="h-4 w-4" />
|
>
|
||||||
</Button>
|
<Trash className="h-4 w-4" />
|
||||||
</TooltipTrigger>
|
</Button>
|
||||||
<TooltipContent>
|
</TooltipTrigger>
|
||||||
<p>Eliminar</p>
|
<TooltipContent>
|
||||||
</TooltipContent>
|
<p>Eliminar</p>
|
||||||
</Tooltip>
|
</TooltipContent>
|
||||||
</TooltipProvider>
|
</Tooltip>
|
||||||
</>
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ export function TrainingViewModal({
|
|||||||
<span className="text-xs font-bold text-muted-foreground block mb-1">
|
<span className="text-xs font-bold text-muted-foreground block mb-1">
|
||||||
DISTRIBUCIÓN INTERNA
|
DISTRIBUCIÓN INTERNA
|
||||||
</span>
|
</span>
|
||||||
<p>Cant: {prod.internalQuantity}</p>
|
<p>Cant: {prod.internalQuantity} {prod.internalUnit}</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{prod.internalDescription}
|
{prod.internalDescription}
|
||||||
</p>
|
</p>
|
||||||
@@ -284,7 +284,7 @@ export function TrainingViewModal({
|
|||||||
<span className="text-xs font-bold text-muted-foreground block mb-1">
|
<span className="text-xs font-bold text-muted-foreground block mb-1">
|
||||||
EXPORTACIÓN ({prod.externalCountry})
|
EXPORTACIÓN ({prod.externalCountry})
|
||||||
</span>
|
</span>
|
||||||
<p>Cant: {prod.externalQuantity}</p>
|
<p>Cant: {prod.externalQuantity} {prod.externalUnit}</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{prod.externalDescription}
|
{prod.externalDescription}
|
||||||
</p>
|
</p>
|
||||||
@@ -360,7 +360,7 @@ export function TrainingViewModal({
|
|||||||
{mat.supplyType}
|
{mat.supplyType}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="secondary">Cant: {mat.quantity}</Badge>
|
<Badge variant="secondary">Cant: {mat.quantity} {mat.unit}</Badge>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{(!data.productionList ||
|
{(!data.productionList ||
|
||||||
|
|||||||
@@ -11,15 +11,18 @@ const productItemSchema = z.object({
|
|||||||
|
|
||||||
// Distribución Interna
|
// Distribución Interna
|
||||||
internalDistributionZone: z.string().optional(),
|
internalDistributionZone: z.string().optional(),
|
||||||
|
internalQuantity: z.coerce.string().or(z.number()).optional(),
|
||||||
|
internalUnit: z.string().optional(),
|
||||||
|
|
||||||
// Distribución Externa
|
// Distribución Externa
|
||||||
externalCountry: z.string().optional(),
|
externalCountry: z.string().optional(),
|
||||||
externalState: z.number().optional(),
|
externalState: z.number().optional().nullable(),
|
||||||
externalMunicipality: z.number().optional(),
|
externalMunicipality: z.number().optional().nullable(),
|
||||||
externalParish: z.number().optional(),
|
externalParish: z.number().optional().nullable(),
|
||||||
externalCity: z.string().optional(),
|
externalCity: z.string().optional(),
|
||||||
externalDescription: z.string().optional(),
|
externalDescription: z.string().optional(),
|
||||||
externalQuantity: z.coerce.string().or(z.number()).optional(),
|
externalQuantity: z.coerce.string().or(z.number()).optional(),
|
||||||
|
externalUnit: z.string().optional(),
|
||||||
|
|
||||||
// Mano de obra
|
// Mano de obra
|
||||||
womenCount: z.coerce.string().or(z.number()).optional(),
|
womenCount: z.coerce.string().or(z.number()).optional(),
|
||||||
@@ -30,6 +33,7 @@ const productionItemSchema = z.object({
|
|||||||
rawMaterial: z.string(),
|
rawMaterial: z.string(),
|
||||||
supplyType: z.string().optional(),
|
supplyType: z.string().optional(),
|
||||||
quantity: z.coerce.string().or(z.number()).optional(), // Aceptamos string o number por los inputs
|
quantity: z.coerce.string().or(z.number()).optional(), // Aceptamos string o number por los inputs
|
||||||
|
unit: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const equipmentItemSchema = z.object({
|
const equipmentItemSchema = z.object({
|
||||||
@@ -152,6 +156,7 @@ export const trainingSchema = z.object({
|
|||||||
photo1: z.string().optional().nullable(),
|
photo1: z.string().optional().nullable(),
|
||||||
photo2: z.string().optional().nullable(),
|
photo2: z.string().optional().nullable(),
|
||||||
photo3: z.string().optional().nullable(),
|
photo3: z.string().optional().nullable(),
|
||||||
|
createdBy: z.number().optional().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TrainingSchema = z.infer<typeof trainingSchema>;
|
export type TrainingSchema = z.infer<typeof trainingSchema>;
|
||||||
|
|||||||
@@ -8,111 +8,111 @@
|
|||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: oklch(0.9824 0.0013 286.3757);
|
--background: oklch(0.9751 0.0127 244.2507);
|
||||||
--foreground: oklch(0.3211 0 0);
|
--foreground: oklch(0.3729 0.0306 259.7328);
|
||||||
--card: oklch(1.0000 0 0);
|
--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: oklch(1.0000 0 0);
|
||||||
--popover-foreground: oklch(0.3211 0 0);
|
--popover-foreground: oklch(0.3729 0.0306 259.7328);
|
||||||
--primary: oklch(0.6487 0.1538 150.3071);
|
--primary: oklch(0.7227 0.1920 149.5793);
|
||||||
--primary-foreground: oklch(1.0000 0 0);
|
--primary-foreground: oklch(1.0000 0 0);
|
||||||
--secondary: oklch(0.6746 0.1414 261.3380);
|
--secondary: oklch(0.9514 0.0250 236.8242);
|
||||||
--secondary-foreground: oklch(1.0000 0 0);
|
--secondary-foreground: oklch(0.4461 0.0263 256.8018);
|
||||||
--muted: oklch(0.8828 0.0285 98.1033);
|
--muted: oklch(0.9670 0.0029 264.5419);
|
||||||
--muted-foreground: oklch(0.5382 0 0);
|
--muted-foreground: oklch(0.5510 0.0234 264.3637);
|
||||||
--accent: oklch(0.8269 0.1080 211.9627);
|
--accent: oklch(0.9505 0.0507 163.0508);
|
||||||
--accent-foreground: oklch(0.3211 0 0);
|
--accent-foreground: oklch(0.3729 0.0306 259.7328);
|
||||||
--destructive: oklch(0.6368 0.2078 25.3313);
|
--destructive: oklch(0.6368 0.2078 25.3313);
|
||||||
--destructive-foreground: oklch(1.0000 0 0);
|
--destructive-foreground: oklch(1.0000 0 0);
|
||||||
--border: oklch(0.8699 0 0);
|
--border: oklch(0.9276 0.0058 264.5313);
|
||||||
--input: oklch(0.8699 0 0);
|
--input: oklch(0.9276 0.0058 264.5313);
|
||||||
--ring: oklch(0.6487 0.1538 150.3071);
|
--ring: oklch(0.7227 0.1920 149.5793);
|
||||||
--chart-1: oklch(0.6487 0.1538 150.3071);
|
--chart-1: oklch(0.7227 0.1920 149.5793);
|
||||||
--chart-2: oklch(0.6746 0.1414 261.3380);
|
--chart-2: oklch(0.6959 0.1491 162.4796);
|
||||||
--chart-3: oklch(0.8269 0.1080 211.9627);
|
--chart-3: oklch(0.5960 0.1274 163.2254);
|
||||||
--chart-4: oklch(0.5880 0.0993 245.7394);
|
--chart-4: oklch(0.5081 0.1049 165.6121);
|
||||||
--chart-5: oklch(0.5905 0.1608 148.2409);
|
--chart-5: oklch(0.4318 0.0865 166.9128);
|
||||||
--sidebar: oklch(0.9824 0.0013 286.3757);
|
--sidebar: oklch(0.9514 0.0250 236.8242);
|
||||||
--sidebar-foreground: oklch(0.3211 0 0);
|
--sidebar-foreground: oklch(0.3729 0.0306 259.7328);
|
||||||
--sidebar-primary: oklch(0.6487 0.1538 150.3071);
|
--sidebar-primary: oklch(0.7227 0.1920 149.5793);
|
||||||
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
||||||
--sidebar-accent: oklch(0.8269 0.1080 211.9627);
|
--sidebar-accent: oklch(0.9505 0.0507 163.0508);
|
||||||
--sidebar-accent-foreground: oklch(0.3211 0 0);
|
--sidebar-accent-foreground: oklch(0.3729 0.0306 259.7328);
|
||||||
--sidebar-border: oklch(0.8699 0 0);
|
--sidebar-border: oklch(0.9276 0.0058 264.5313);
|
||||||
--sidebar-ring: oklch(0.6487 0.1538 150.3071);
|
--sidebar-ring: oklch(0.7227 0.1920 149.5793);
|
||||||
--font-sans: Plus Jakarta Sans, sans-serif;
|
--font-sans: DM Sans, sans-serif;
|
||||||
--font-serif: Source Serif 4, serif;
|
--font-serif: Lora, serif;
|
||||||
--font-mono: JetBrains Mono, monospace;
|
--font-mono: IBM Plex Mono, monospace;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
--shadow-x: 0;
|
--shadow-x: 0px;
|
||||||
--shadow-y: 1px;
|
--shadow-y: 4px;
|
||||||
--shadow-blur: 3px;
|
--shadow-blur: 8px;
|
||||||
--shadow-spread: 0px;
|
--shadow-spread: -1px;
|
||||||
--shadow-opacity: 0.1;
|
--shadow-opacity: 0.1;
|
||||||
--shadow-color: oklch(0 0 0);
|
--shadow-color: hsl(0 0% 0%);
|
||||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
--shadow-2xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05);
|
||||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
--shadow-xs: 0px 4px 8px -1px 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-sm: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px 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: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px 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-md: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px 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-lg: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px 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-xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10);
|
||||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
--shadow-2xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.25);
|
||||||
--tracking-normal: 0em;
|
--tracking-normal: 0em;
|
||||||
--spacing: 0.25rem;
|
--spacing: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.2303 0.0125 264.2926);
|
--background: oklch(0.2077 0.0398 265.7549);
|
||||||
--foreground: oklch(0.9219 0 0);
|
--foreground: oklch(0.8717 0.0093 258.3382);
|
||||||
--card: oklch(0.3210 0.0078 223.6661);
|
--card: oklch(0.2795 0.0368 260.0310);
|
||||||
--card-foreground: oklch(0.9219 0 0);
|
--card-foreground: oklch(0.8717 0.0093 258.3382);
|
||||||
--popover: oklch(0.3210 0.0078 223.6661);
|
--popover: oklch(0.2795 0.0368 260.0310);
|
||||||
--popover-foreground: oklch(0.9219 0 0);
|
--popover-foreground: oklch(0.8717 0.0093 258.3382);
|
||||||
--primary: oklch(0.6487 0.1538 150.3071);
|
--primary: oklch(0.7729 0.1535 163.2231);
|
||||||
--primary-foreground: oklch(1.0000 0 0);
|
--primary-foreground: oklch(0.2077 0.0398 265.7549);
|
||||||
--secondary: oklch(0.5880 0.0993 245.7394);
|
--secondary: oklch(0.3351 0.0331 260.9120);
|
||||||
--secondary-foreground: oklch(0.9219 0 0);
|
--secondary-foreground: oklch(0.7118 0.0129 286.0665);
|
||||||
--muted: oklch(0.3867 0 0);
|
--muted: oklch(0.2463 0.0275 259.9628);
|
||||||
--muted-foreground: oklch(0.7155 0 0);
|
--muted-foreground: oklch(0.5510 0.0234 264.3637);
|
||||||
--accent: oklch(0.6746 0.1414 261.3380);
|
--accent: oklch(0.3729 0.0306 259.7328);
|
||||||
--accent-foreground: oklch(0.9219 0 0);
|
--accent-foreground: oklch(0.7118 0.0129 286.0665);
|
||||||
--destructive: oklch(0.6368 0.2078 25.3313);
|
--destructive: oklch(0.6368 0.2078 25.3313);
|
||||||
--destructive-foreground: oklch(1.0000 0 0);
|
--destructive-foreground: oklch(0.2077 0.0398 265.7549);
|
||||||
--border: oklch(0.3867 0 0);
|
--border: oklch(0.4461 0.0263 256.8018);
|
||||||
--input: oklch(0.3867 0 0);
|
--input: oklch(0.4461 0.0263 256.8018);
|
||||||
--ring: oklch(0.6487 0.1538 150.3071);
|
--ring: oklch(0.7729 0.1535 163.2231);
|
||||||
--chart-1: oklch(0.6487 0.1538 150.3071);
|
--chart-1: oklch(0.7729 0.1535 163.2231);
|
||||||
--chart-2: oklch(0.5880 0.0993 245.7394);
|
--chart-2: oklch(0.7845 0.1325 181.9120);
|
||||||
--chart-3: oklch(0.6746 0.1414 261.3380);
|
--chart-3: oklch(0.7227 0.1920 149.5793);
|
||||||
--chart-4: oklch(0.8269 0.1080 211.9627);
|
--chart-4: oklch(0.6959 0.1491 162.4796);
|
||||||
--chart-5: oklch(0.5905 0.1608 148.2409);
|
--chart-5: oklch(0.5960 0.1274 163.2254);
|
||||||
--sidebar: oklch(0.2303 0.0125 264.2926);
|
--sidebar: oklch(0.2795 0.0368 260.0310);
|
||||||
--sidebar-foreground: oklch(0.9219 0 0);
|
--sidebar-foreground: oklch(0.8717 0.0093 258.3382);
|
||||||
--sidebar-primary: oklch(0.6487 0.1538 150.3071);
|
--sidebar-primary: oklch(0.7729 0.1535 163.2231);
|
||||||
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
--sidebar-primary-foreground: oklch(0.2077 0.0398 265.7549);
|
||||||
--sidebar-accent: oklch(0.6746 0.1414 261.3380);
|
--sidebar-accent: oklch(0.3729 0.0306 259.7328);
|
||||||
--sidebar-accent-foreground: oklch(0.9219 0 0);
|
--sidebar-accent-foreground: oklch(0.7118 0.0129 286.0665);
|
||||||
--sidebar-border: oklch(0.3867 0 0);
|
--sidebar-border: oklch(0.4461 0.0263 256.8018);
|
||||||
--sidebar-ring: oklch(0.6487 0.1538 150.3071);
|
--sidebar-ring: oklch(0.7729 0.1535 163.2231);
|
||||||
--font-sans: Plus Jakarta Sans, sans-serif;
|
--font-sans: DM Sans, sans-serif;
|
||||||
--font-serif: Source Serif 4, serif;
|
--font-serif: Lora, serif;
|
||||||
--font-mono: JetBrains Mono, monospace;
|
--font-mono: IBM Plex Mono, monospace;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
--shadow-x: 0;
|
--shadow-x: 0px;
|
||||||
--shadow-y: 1px;
|
--shadow-y: 4px;
|
||||||
--shadow-blur: 3px;
|
--shadow-blur: 8px;
|
||||||
--shadow-spread: 0px;
|
--shadow-spread: -1px;
|
||||||
--shadow-opacity: 0.1;
|
--shadow-opacity: 0.1;
|
||||||
--shadow-color: oklch(0 0 0);
|
--shadow-color: hsl(0 0% 0%);
|
||||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
--shadow-2xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.05);
|
||||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
--shadow-xs: 0px 4px 8px -1px 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-sm: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px 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: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px 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-md: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px 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-lg: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px 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-xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10);
|
||||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
--shadow-2xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
@@ -166,73 +166,12 @@
|
|||||||
--shadow-lg: var(--shadow-lg);
|
--shadow-lg: var(--shadow-lg);
|
||||||
--shadow-xl: var(--shadow-xl);
|
--shadow-xl: var(--shadow-xl);
|
||||||
--shadow-2xl: var(--shadow-2xl);
|
--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 {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@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 {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user