diff --git a/package-lock.json b/package-lock.json index 3142de1..7d4437d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", + "@radix-ui/react-tooltip": "^1.1.3", "axios": "^1.7.7", "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", @@ -3831,6 +3832,40 @@ } } }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz", + "integrity": "sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", diff --git a/package.json b/package.json index 452fd1e..df4a6f5 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", + "@radix-ui/react-tooltip": "^1.1.3", "axios": "^1.7.7", "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", diff --git a/src/app/api/[...]/asset.js b/src/app/api/[...]/asset.js index fb9a888..e5dbe72 100644 --- a/src/app/api/[...]/asset.js +++ b/src/app/api/[...]/asset.js @@ -1,7 +1,7 @@ import { paramsValidate, queryValidate } from "@/lib/validators/data-validate"; import { assetIdSchema, queryAssetDownloadSchema, queryAssetListSchema } from "@/lib/validators/data-validate/assets"; import NextApiRouter from "@billyen2012/next-api-router"; -import { archive, create, download, get, list, update } from "../_lib/controllers/assetsController"; +import { archive, create, download, get, list, publish, update } from "../_lib/controllers/assetsController"; import validate from "@/lib/validators/validate"; import { authorize } from "../_lib/middlewares/authorize"; import { userRoles } from "@/lib/utils/constants"; @@ -54,6 +54,13 @@ router.get('/archive/:assetId', archive ) +router.get('/publish/:assetId', + authorize([userRoles.ADMIN, userRoles.GESTOR]), + paramsValidate(assetIdSchema), + validate, + publish +) + router.get('/download', queryValidate(queryAssetDownloadSchema), validate, diff --git a/src/app/api/_lib/controllers/assetsController.js b/src/app/api/_lib/controllers/assetsController.js index 241b24f..3d7c53c 100644 --- a/src/app/api/_lib/controllers/assetsController.js +++ b/src/app/api/_lib/controllers/assetsController.js @@ -88,7 +88,7 @@ export const create = async function (req, res) { const asset = await Asset.create(data); - return res.status(200).json({ message: 'ok' }); + return res.status(200).json({ asset }); } catch (error) { console.error(error) return res.status(500).json({ message: messages.error.default }) @@ -136,6 +136,25 @@ export const archive = async function (req, res) { } } +export const publish = async function (req, res) { + try { + const assetId = req.params.assetId; + const now = new Date() + const asset = await Asset.findById(assetId) + if (asset.publish) { + asset.set({ publish: false }) + } else { + asset.set({ publish: true }) + } + await asset.save() + + return res.status(200).json(asset); + } catch (error) { + console.error(error); + return res.status(500).json({ message: messages.error.default }); + } +} + export const download = async function (req, res) { try { diff --git a/src/app/layout.jsx b/src/app/layout.jsx index fe17c64..e712184 100644 --- a/src/app/layout.jsx +++ b/src/app/layout.jsx @@ -2,6 +2,7 @@ import { Inter } from "next/font/google"; import "./globals.css"; import Navbar from "@/components/layout/navbar"; import { Toaster } from "@/components/ui/toaster"; +import { TooltipProvider } from "@radix-ui/react-tooltip"; const inter = Inter({ subsets: ["latin"] }); @@ -14,9 +15,11 @@ export default function RootLayout({ children }) { return ( - - {children} - + + + {children} + + ); diff --git a/src/app/page.jsx b/src/app/page.jsx index f7a0194..c7336c0 100644 --- a/src/app/page.jsx +++ b/src/app/page.jsx @@ -9,16 +9,15 @@ import DownloadButton from "@/components/admin/asset/download-button"; export const dynamic = "force-dynamic"; -export default function Home({ searchParams }) { +export default function Home({ searchParams: { estado, search, page } }) { + const destination = estado; return (

Buscador de bienes

- +
diff --git a/src/components/admin/asset/asset-list.jsx b/src/components/admin/asset/asset-list.jsx index 76b8657..cc7cbce 100644 --- a/src/components/admin/asset/asset-list.jsx +++ b/src/components/admin/asset/asset-list.jsx @@ -1,7 +1,7 @@ import React from "react"; -import AssetCard from "./asset-card"; import { getAssets } from "@/lib/actions/home/fetch-data"; import AssetPagination from "@/components/asset-pagination"; +import AssetCard from "./assetCard"; async function AssetList({ filter = {} }) { const { assets, page, total, pages, nextPage, prevPage, status, message } = diff --git a/src/components/admin/asset/asset-serch.jsx b/src/components/admin/asset/asset-serch.jsx index dbc7be9..169b94c 100644 --- a/src/components/admin/asset/asset-serch.jsx +++ b/src/components/admin/asset/asset-serch.jsx @@ -8,6 +8,8 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { FilterIcon } from "lucide-react"; @@ -16,6 +18,7 @@ const AssetSerch = () => { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); + const [filter, setFilter] = useState("todos"); const [searchValue, setSearchValue] = useState( searchParams.get("search") || "" ); @@ -49,6 +52,23 @@ const AssetSerch = () => { router.push(`${pathname}${query}`); }; + + const handleFilter = (value) => { + const params = new URLSearchParams(searchParams.entries()); + + if (!value) { + params.delete("estado"); + } else { + params.set("estado", value); + params.delete("page"); + } + const estado = params.toString(); + const query = estado ? `?${estado}` : ""; + + router.push(`${pathname}${query}`); + setFilter(value); + }; + return (
@@ -61,20 +81,31 @@ const AssetSerch = () => { className="shadow-md rounded-lg" />
-
- - - - Filtrar - - - - Supiti - Upiti - Uapiti - - -
+ {!pathname.startsWith("/admin") && ( +
+ + + + Filtrar + + + + + Todos + + Subasta + + + Reutilizacion + + + + +
+ )}
); }; diff --git a/src/components/admin/asset/assetCard/card-actions.jsx b/src/components/admin/asset/assetCard/card-actions.jsx new file mode 100644 index 0000000..d042cfb --- /dev/null +++ b/src/components/admin/asset/assetCard/card-actions.jsx @@ -0,0 +1,77 @@ +import React from "react"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { Pencil, Archive, ArchiveX, BookX, BookUp } from "lucide-react"; +import Link from "next/link"; +import { + archiveAsset, + togglePublish, +} from "@/lib/actions/admin/asset-actions/asset"; +import { ToastAction } from "@/components/ui/toast"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { useToast } from "@/components/ui/use-toast"; + +const CardActions = ({ asset, pathname }) => { + const { toast } = useToast(); + return ( +
+ + + + + + + +

Editar

+
+
+ + +
+ toast({ + description: `Confirmar para ${ + pathname.includes("archivados") ? "des" : "" + }archivar el bien`, + action: ( + archiveAsset(asset._id)} + altText="Confirmar" + > + Confirmar + + ), + }) + } + > + {pathname.includes("archivados") ? : } +
+
+ +

{asset.archivedAt ? "Desarchivar" : "Archivar"}

+
+
+ + +
{ + togglePublish(asset._id); + }} + className="submitButton w-full my-2" + > + {asset.publish ? : } +
+
+ +

{asset.publish ? "Despublicar" : "Publicar"}

+
+
+
+ ); +}; + +export default CardActions; diff --git a/src/components/admin/asset/asset-card.jsx b/src/components/admin/asset/assetCard/index.jsx similarity index 81% rename from src/components/admin/asset/asset-card.jsx rename to src/components/admin/asset/assetCard/index.jsx index 07cfa56..b61baf5 100644 --- a/src/components/admin/asset/asset-card.jsx +++ b/src/components/admin/asset/assetCard/index.jsx @@ -1,4 +1,4 @@ -'use client' +"use client"; import React, { useEffect, useState } from "react"; import { Card, @@ -9,18 +9,20 @@ import { } from "@/components/ui/card"; import { fontAwesomeIcons, showCardOptions } from "@/lib/utils/constants"; import { Button, buttonVariants } from "@/components/ui/button"; -import { Pencil, Archive, ArchiveX } from "lucide-react"; +import { Pencil, Archive, ArchiveX, BookX, BookUp } from "lucide-react"; import Link from "next/link"; -import { archiveAsset } from "@/lib/actions/admin/asset-actions/asset"; -import { useToast } from "@/components/ui/use-toast"; -import { ToastAction } from "@/components/ui/toast"; +import { + archiveAsset, + togglePublish, +} from "@/lib/actions/admin/asset-actions/asset"; import { usePathname } from "next/navigation"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; +import CardActions from "./card-actions"; + const AssetCard = ({ asset }) => { const pathname = usePathname(); const [showMore, setShowMore] = useState(false); - const { toast } = useToast(); const renderFieldShowCard = (fields, ordering) => { return ( @@ -125,34 +127,7 @@ const AssetCard = ({ asset }) => {
-
- - - - -
+ {asset.extras[showCardOptions.EXPANDED.value] && ( diff --git a/src/components/admin/asset/assetForm/destination-info/index.jsx b/src/components/admin/asset/assetForm/destination-info/index.jsx index fec6ea7..af4f244 100644 --- a/src/components/admin/asset/assetForm/destination-info/index.jsx +++ b/src/components/admin/asset/assetForm/destination-info/index.jsx @@ -87,6 +87,7 @@ function DestinationInfo({ assetEdit }) { /> diff --git a/src/components/admin/asset/assetForm/index.jsx b/src/components/admin/asset/assetForm/index.jsx index 04ed0bf..3cba4a4 100644 --- a/src/components/admin/asset/assetForm/index.jsx +++ b/src/components/admin/asset/assetForm/index.jsx @@ -15,6 +15,7 @@ import { editAsset, saveAsset, } from "@/lib/actions/admin/asset-actions/asset-client"; +import { togglePublish } from "@/lib/actions/admin/asset-actions/asset"; const FormAsset = ({ assetEdit }) => { const router = useRouter(); @@ -43,12 +44,18 @@ const FormAsset = ({ assetEdit }) => { }, ]; - const [tab, setActiveTab] = useState([assetFormSteps[0].slug]); + const [tab, setActiveTab] = useState(assetFormSteps[0].slug); const [incompleteTab, setIncompleteTab] = useState(null); const handleTab = (value) => { setActiveTab(value); }; + const handleNextStep = (value) => { + const currentStep = assetFormSteps.find((step) => step.slug === tab); + const index = assetFormSteps.indexOf(currentStep); + const step = value === "next" ? index + 1 : index - 1; + setActiveTab(assetFormSteps[step].slug); + }; const submit = async (event) => { event.preventDefault(); @@ -99,12 +106,12 @@ const FormAsset = ({ assetEdit }) => { if (form.checkValidity()) { try { - let asset; - if (assetEdit) asset = await editAsset(assetEdit._id, realFormData); - else asset = await saveAsset(realFormData); + let resp; + if (assetEdit) resp = await editAsset(assetEdit._id, realFormData); + else resp = await saveAsset(realFormData); - if (asset.status === 200) { - router.push("/admin/bien"); + if (resp.status === 200) { + router.push(`/admin/bien/editar/${resp.asset._id}`); router.refresh(); } } catch (err) { @@ -153,9 +160,9 @@ const FormAsset = ({ assetEdit }) => { }; return ( -
-
-
+
+ +
{assetFormSteps.map((step, idx) => renderTabTrigger(step, idx))} @@ -173,12 +180,47 @@ const FormAsset = ({ assetEdit }) => { ))} - - - -
+
+ {assetFormSteps[0].slug !== tab && ( + + )} + {assetFormSteps.slice(-1)[0].slug !== tab && ( + + )} +
+
+
+ {(assetFormSteps.slice(-1)[0].slug === tab || assetEdit) && ( + + )} + {assetEdit && ( + + )} +
+
); }; diff --git a/src/components/admin/asset/assetForm/judicial-process/index.jsx b/src/components/admin/asset/assetForm/judicial-process/index.jsx index 638d9f8..fb6e1d1 100644 --- a/src/components/admin/asset/assetForm/judicial-process/index.jsx +++ b/src/components/admin/asset/assetForm/judicial-process/index.jsx @@ -98,7 +98,10 @@ const JudicialInfo = ({ assetEdit }) => { name="cautelaResolution" value={assetEdit.cautelaResolution} /> -
@@ -168,6 +171,7 @@ const JudicialInfo = ({ assetEdit }) => { /> diff --git a/src/components/admin/category/category-field-form.jsx b/src/components/admin/category/category-field-form.jsx index 1bb4226..87bf0f2 100644 --- a/src/components/admin/category/category-field-form.jsx +++ b/src/components/admin/category/category-field-form.jsx @@ -219,6 +219,7 @@ const CategoryFieldForm = ({ setExtras, extraFieldsEdit, errors }) => { onClick={() => addInputToEditFile(`selectablesOptions-${idx}`) } + variant="link" > Cambiar diff --git a/src/components/ui/tooltip.jsx b/src/components/ui/tooltip.jsx new file mode 100644 index 0000000..84147e0 --- /dev/null +++ b/src/components/ui/tooltip.jsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/src/lib/actions/admin/asset-actions/asset.js b/src/lib/actions/admin/asset-actions/asset.js index ee64bab..45106f8 100644 --- a/src/lib/actions/admin/asset-actions/asset.js +++ b/src/lib/actions/admin/asset-actions/asset.js @@ -1,12 +1,14 @@ "use server" import { revalidatePath } from "next/cache"; import fetchData from "@/lib/utils/get-data"; -import axiosServices from "@/lib/utils/axios"; export const archiveAsset = async (id) => { revalidatePath(`/admin/bien`) return await fetchData(`/api/asset/archive/${id}`) - } +export const togglePublish = async (id) => { + revalidatePath(`/admin/bien`) + return await fetchData(`/api/asset/publish/${id}`) +} diff --git a/src/lib/models/Asset.js b/src/lib/models/Asset.js index 836c454..10140f1 100644 --- a/src/lib/models/Asset.js +++ b/src/lib/models/Asset.js @@ -110,6 +110,10 @@ export const AssetSchema = new mongoose.Schema( default: undefined }, causeCoverSheet: { type: String }, + publish: { + type: Boolean, + default: false, + }, archivedAt: { type: Date, required: false,