Fase 3: Implementación de Listings (Avisos) - Entidades, Repositorio, API y Frontend Wizard integración

This commit is contained in:
2025-12-17 13:51:48 -03:00
parent f1bd25ea79
commit 1b88394b00
12 changed files with 353 additions and 4 deletions

View File

@@ -1,10 +1,21 @@
import { useEffect, useState } from 'react';
import { useWizardStore } from './store/wizardStore';
import CategorySelection from './pages/Steps/CategorySelection';
import OperationSelection from './pages/Steps/OperationSelection';
import AttributeForm from './pages/Steps/AttributeForm';
import SummaryStep from './pages/Steps/SummaryStep';
import { wizardService } from './services/wizardService';
import type { AttributeDefinition } from './types';
function App() {
const step = useWizardStore((state) => state.step);
const { step, selectedCategory } = useWizardStore();
const [definitions, setDefinitions] = useState<AttributeDefinition[]>([]);
useEffect(() => {
if (selectedCategory) {
wizardService.getAttributes(selectedCategory.id).then(setDefinitions);
}
}, [selectedCategory]);
return (
<div className="min-h-screen bg-slate-50 text-slate-900 font-sans">
@@ -30,8 +41,8 @@ function App() {
{step === 1 && <CategorySelection />}
{step === 2 && <OperationSelection />}
{step === 3 && <AttributeForm />}
{step === 4 && <div className="text-center py-20">Paso 4: Fotos (Coming Soon)</div>}
{step === 5 && <div className="text-center py-20">Paso 5: Resumen y Pago (Coming Soon)</div>}
{step === 4 && <div className="text-center py-20">Paso 4: Fotos (Coming Soon) - <button onClick={() => useWizardStore.getState().setStep(5)} className="text-blue-500 underline">Saltar</button></div>}
{step === 5 && <SummaryStep definitions={definitions} />}
</main>
</div>
);

View File

@@ -0,0 +1,108 @@
import { useState } from 'react';
import { useWizardStore } from '../../store/wizardStore';
import { wizardService } from '../../services/wizardService';
import { StepWrapper } from '../../components/StepWrapper';
import type { AttributeDefinition } from '../../types';
export default function SummaryStep({ definitions }: { definitions: AttributeDefinition[] }) {
const { selectedCategory, selectedOperation, attributes, setStep } = useWizardStore();
const [isSubmitting, setIsSubmitting] = useState(false);
const [createdId, setCreatedId] = useState<number | null>(null);
const handlePublish = async () => {
if (!selectedCategory || !selectedOperation) return;
setIsSubmitting(true);
try {
const attributePayload: Record<number, string> = {};
// Ideally we should have stored definitions in store or passed them.
// For now assuming 'definitions' prop contains current category definitions
// For now assuming 'definitions' prop contains current category definitions
definitions.forEach(def => {
if (attributes[def.name]) {
attributePayload[def.id] = attributes[def.name].toString();
}
});
const payload = {
categoryId: selectedCategory.id,
operationId: selectedOperation.id,
title: attributes['title'],
description: 'Generated via Wizard', // Todo: Add description field
price: parseFloat(attributes['price']),
currency: 'ARS',
attributes: attributePayload
};
const result = await wizardService.createListing(payload);
setCreatedId(result.id);
} catch (error) {
console.error(error);
alert('Error al publicar');
} finally {
setIsSubmitting(false);
}
};
if (createdId) {
return (
<StepWrapper>
<div className="text-center py-10">
<div className="text-4xl text-green-500 mb-4"></div>
<h2 className="text-2xl font-bold mb-2">¡Aviso Publicado!</h2>
<p className="text-gray-500">ID de referencia: #{createdId}</p>
<button onClick={() => window.location.reload()} className="mt-8 text-brand-600 underline">Publicar otro</button>
</div>
</StepWrapper>
);
}
return (
<StepWrapper>
<h2 className="text-2xl font-bold mb-6 text-brand-900">Resumen y Confirmación</h2>
<div className="bg-white p-6 rounded-xl border border-slate-200 shadow-sm mb-6">
<div className="mb-4">
<span className="block text-xs text-slate-500 uppercase tracking-wide">Categoría</span>
<span className="font-semibold text-lg">{selectedCategory?.name}</span>
</div>
<div className="mb-4">
<span className="block text-xs text-slate-500 uppercase tracking-wide">Operación</span>
<span className="font-semibold text-lg">{selectedOperation?.name}</span>
</div>
<div className="border-t pt-4 mt-4">
<h3 className="font-bold mb-2">{attributes['title']}</h3>
<div className="text-2xl font-bold text-green-600 mb-4">$ {attributes['price']}</div>
<div className="grid grid-cols-2 gap-2 text-sm">
{definitions.map(def => attributes[def.name] && (
<div key={def.id}>
<span className="text-slate-500">{def.name}:</span> <span className="font-medium">{attributes[def.name]}</span>
</div>
))}
</div>
</div>
</div>
<div className="flex gap-4">
<button
onClick={() => setStep(3)}
className="flex-1 py-3 text-slate-600 hover:bg-slate-100 rounded-lg"
disabled={isSubmitting}
>
Volver
</button>
<button
onClick={handlePublish}
disabled={isSubmitting}
className="flex-1 py-3 bg-brand-600 text-white font-bold rounded-lg hover:bg-brand-700 disabled:opacity-50"
>
{isSubmitting ? 'Publicando...' : 'Confirmar y Publicar'}
</button>
</div>
</StepWrapper>
);
}

View File

@@ -1,5 +1,5 @@
import api from './api';
import { Category, Operation, AttributeDefinition } from '../types';
import type { Category, Operation, AttributeDefinition } from '../types'; // ID: type-import-fix
export const wizardService = {
getCategories: async (): Promise<Category[]> => {
@@ -15,5 +15,10 @@ export const wizardService = {
getAttributes: async (categoryId: number): Promise<AttributeDefinition[]> => {
const response = await api.get<AttributeDefinition[]>(`/attributedefinitions/category/${categoryId}`);
return response.data;
},
createListing: async (data: any): Promise<{ id: number }> => {
const response = await api.post<{ id: number }>('/listings', data);
return response.data;
}
};

View File

@@ -0,0 +1,15 @@
export interface CreateListingDto {
categoryId: number;
operationId: number;
title: string;
description: string;
price: number;
currency: string;
userId?: number;
attributes: Record<string, string>; // definitionId -> Value
}
export interface Listing {
id: number;
// ...
}