Prelegent widzi zadania i banery na sali
`PublicTaskOfferController` szukal tokenu wylacznie w `registrations`, a prelegent tam nie istnieje: nie przechodzi przez formularz zapisu i ma wlasny token w `webinar_speakers`. Kazdy endpoint sali odpowiadal mu 404. Objawialo sie to tym, ze prowadzacy **nie widzial wlasnych zadan**: panel pokazywal "Nie znaleziono zapisu" przy zadaniu ogloszonym minute wczesniej i widocznym w panelu prowadzenia. Tak samo znikaly banery i nie liczyly sie ich wyswietlenia. Zdarzylo sie to na zywo, w trakcie spotkania. Nowa metoda `TaskService.viewer` obsluguje oba rodzaje tokenu, wzorowana na `PublicInteractionController`, ktory robil to juz przy historii czatu. Przelaczone sa WYLACZNIE odczyty: lista zadan, banery i ich zdarzenia. Zapisy (zgloszenie do zadania, wyslanie oferty) zostaja przy `participant`, bo prelegent nie zapisuje sie na wlasne zadanie. `registrationId` rowne null jest tu poprawna wartoscia, nie brakiem danych: `listViews` przewidywalo to od poczatku, a `banner_events.registration_id` jest nullowalne wlasnie dla zdarzen od osob bez zapisu. Front: `TasksTab` dostaje tryb `readOnly`, wiec prowadzacy widzi zadania i liczbe zapisanych, ale bez przycisku zgloszenia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+8
-4
@@ -67,7 +67,9 @@ public class PublicTaskOfferController {
|
|||||||
@GetMapping("/tasks")
|
@GetMapping("/tasks")
|
||||||
@Operation(summary = "Zadania webinaru i wlasne zgloszenia")
|
@Operation(summary = "Zadania webinaru i wlasne zgloszenia")
|
||||||
public TasksView taskList(@PathVariable String token) {
|
public TasksView taskList(@PathVariable String token) {
|
||||||
TaskService.ParticipantDetails participant = participant(token);
|
// odczyt, wiec takze dla prelegenta: prowadzacy ma widziec zadania,
|
||||||
|
// ktore sam oglosil
|
||||||
|
TaskService.ParticipantDetails participant = tasks.viewer(token);
|
||||||
return new TasksView(tasks.listViews(participant.webinarId(), participant.registrationId()));
|
return new TasksView(tasks.listViews(participant.webinarId(), participant.registrationId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +155,7 @@ public class PublicTaskOfferController {
|
|||||||
@GetMapping("/banners")
|
@GetMapping("/banners")
|
||||||
@Operation(summary = "Banery obowiazujace w tej chwili dla tego webinaru")
|
@Operation(summary = "Banery obowiazujace w tej chwili dla tego webinaru")
|
||||||
public BannersView activeBanners(@PathVariable String token) {
|
public BannersView activeBanners(@PathVariable String token) {
|
||||||
TaskService.ParticipantDetails participant = participant(token);
|
TaskService.ParticipantDetails participant = tasks.viewer(token);
|
||||||
return new BannersView(
|
return new BannersView(
|
||||||
banners.activeFor(participant.webinarId()),
|
banners.activeFor(participant.webinarId()),
|
||||||
banners.rotationSeconds());
|
banners.rotationSeconds());
|
||||||
@@ -169,7 +171,9 @@ public class PublicTaskOfferController {
|
|||||||
@PostMapping("/banners/{bannerId}/impression")
|
@PostMapping("/banners/{bannerId}/impression")
|
||||||
@Operation(summary = "Zapis wyswietlenia banera, raz na uczestnika")
|
@Operation(summary = "Zapis wyswietlenia banera, raz na uczestnika")
|
||||||
public void bannerImpression(@PathVariable String token, @PathVariable Long bannerId) {
|
public void bannerImpression(@PathVariable String token, @PathVariable Long bannerId) {
|
||||||
TaskService.ParticipantDetails participant = participant(token);
|
// takze prelegent: `banner_events.registration_id` jest nullowalne
|
||||||
|
// wlasnie po to, bo zdarzenie moze pochodzic od osoby bez zapisu
|
||||||
|
TaskService.ParticipantDetails participant = tasks.viewer(token);
|
||||||
banners.recordImpression(bannerId, participant.registrationId());
|
banners.recordImpression(bannerId, participant.registrationId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +181,7 @@ public class PublicTaskOfferController {
|
|||||||
@PostMapping("/banners/{bannerId}/click")
|
@PostMapping("/banners/{bannerId}/click")
|
||||||
@Operation(summary = "Zapis klikniecia w baner")
|
@Operation(summary = "Zapis klikniecia w baner")
|
||||||
public void bannerClick(@PathVariable String token, @PathVariable Long bannerId) {
|
public void bannerClick(@PathVariable String token, @PathVariable Long bannerId) {
|
||||||
TaskService.ParticipantDetails participant = participant(token);
|
TaskService.ParticipantDetails participant = tasks.viewer(token);
|
||||||
banners.recordClick(bannerId, participant.registrationId());
|
banners.recordClick(bannerId, participant.registrationId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,19 +40,22 @@ public class TaskService {
|
|||||||
private final WebinarRepository webinarRepository;
|
private final WebinarRepository webinarRepository;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final PlainTextSanitizer sanitizer;
|
private final PlainTextSanitizer sanitizer;
|
||||||
|
private final com.softspm.webinar.repository.WebinarSpeakerRepository speakerRepository;
|
||||||
|
|
||||||
public TaskService(TaskRepository taskRepository,
|
public TaskService(TaskRepository taskRepository,
|
||||||
InteractionRepository interactionRepository,
|
InteractionRepository interactionRepository,
|
||||||
RegistrationRepository registrationRepository,
|
RegistrationRepository registrationRepository,
|
||||||
WebinarRepository webinarRepository,
|
WebinarRepository webinarRepository,
|
||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
PlainTextSanitizer sanitizer) {
|
PlainTextSanitizer sanitizer,
|
||||||
|
com.softspm.webinar.repository.WebinarSpeakerRepository speakerRepository) {
|
||||||
this.taskRepository = taskRepository;
|
this.taskRepository = taskRepository;
|
||||||
this.interactionRepository = interactionRepository;
|
this.interactionRepository = interactionRepository;
|
||||||
this.registrationRepository = registrationRepository;
|
this.registrationRepository = registrationRepository;
|
||||||
this.webinarRepository = webinarRepository;
|
this.webinarRepository = webinarRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.sanitizer = sanitizer;
|
this.sanitizer = sanitizer;
|
||||||
|
this.speakerRepository = speakerRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -265,6 +268,43 @@ public class TaskService {
|
|||||||
* Odczyt jest w serwisie, a nie w kontrolerze: @Transactional na metodzie
|
* Odczyt jest w serwisie, a nie w kontrolerze: @Transactional na metodzie
|
||||||
* wolanej z tej samej klasy nie dziala, bo samowywolanie omija proxy Springa.
|
* wolanej z tej samej klasy nie dziala, bo samowywolanie omija proxy Springa.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Osoba na sali: uczestnik ALBO prelegent.
|
||||||
|
*
|
||||||
|
* DWA RODZAJE TOKENU, JEDNO WEJSCIE
|
||||||
|
* -----------------------------------------------------------------------
|
||||||
|
* Prelegent nie jest wierszem w `registrations`: nie przechodzi przez
|
||||||
|
* formularz zapisu i ma wlasny token w `webinar_speakers`. Metoda
|
||||||
|
* `participant` szuka wylacznie zapisu, wiec kazdy endpoint sali oparty
|
||||||
|
* na niej odpowiadal prelegentowi bledem 404. Objawialo sie to tym, ze
|
||||||
|
* prowadzacy **nie widzial wlasnych zadan** ani banerow, a panel pokazywal
|
||||||
|
* "Nie znaleziono zapisu" przy poprawnie ogloszonym zadaniu.
|
||||||
|
*
|
||||||
|
* `registrationId` rowne null oznacza prelegenta i jest **poprawna
|
||||||
|
* wartoscia**, nie brakiem danych: `listViews` traktuje go jako osobe bez
|
||||||
|
* wlasnych zgloszen, bo prelegent nie zapisuje sie na wlasne zadanie.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public ParticipantDetails viewer(String token) {
|
||||||
|
return registrationRepository.findByConfirmationToken(token)
|
||||||
|
.map(registration -> new ParticipantDetails(
|
||||||
|
registration.getId(),
|
||||||
|
registration.getWebinar().getId(),
|
||||||
|
(registration.getFirstName() + " " + registration.getLastName()).trim(),
|
||||||
|
registration.getEmail(),
|
||||||
|
registration.getPhone(),
|
||||||
|
registration.getCompany()))
|
||||||
|
.orElseGet(() -> speakerRepository.findByAccessToken(token)
|
||||||
|
.map(speaker -> new ParticipantDetails(
|
||||||
|
null,
|
||||||
|
speaker.getWebinar().getId(),
|
||||||
|
speaker.getFullName(),
|
||||||
|
speaker.getEmail(),
|
||||||
|
null,
|
||||||
|
null))
|
||||||
|
.orElseThrow(() -> new NotFoundException("Nie znaleziono zapisu")));
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public ParticipantDetails participant(String confirmationToken) {
|
public ParticipantDetails participant(String confirmationToken) {
|
||||||
Registration registration = registrationRepository.findByConfirmationToken(confirmationToken)
|
Registration registration = registrationRepository.findByConfirmationToken(confirmationToken)
|
||||||
|
|||||||
@@ -161,7 +161,9 @@ export function InteractionPanel({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{tab === 'tasks' && <TasksTab token={token} webinarId={history.webinarId} />}
|
{tab === 'tasks' && (
|
||||||
|
<TasksTab token={token} webinarId={history.webinarId} readOnly={history.speaker} />
|
||||||
|
)}
|
||||||
{tab === 'offer' && !history.speaker && <OfferTab token={token} />}
|
{tab === 'offer' && !history.speaker && <OfferTab token={token} />}
|
||||||
|
|
||||||
{tab === 'chat' ? (
|
{tab === 'chat' ? (
|
||||||
|
|||||||
@@ -25,7 +25,21 @@ export type TaskView = {
|
|||||||
createdAt: string
|
createdAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TasksTab({ token, webinarId }: { token: string; webinarId: number }) {
|
export function TasksTab({
|
||||||
|
token,
|
||||||
|
webinarId,
|
||||||
|
readOnly = false,
|
||||||
|
}: {
|
||||||
|
token: string
|
||||||
|
webinarId: number
|
||||||
|
/**
|
||||||
|
* Podglad bez mozliwosci zapisu.
|
||||||
|
*
|
||||||
|
* Prowadzacy widzi zadania, ktore sam oglosil, ale nie zapisuje sie na nie:
|
||||||
|
* nie ma wiersza w `registrations`, wiec backend i tak odrzucilby zgloszenie.
|
||||||
|
*/
|
||||||
|
readOnly?: boolean
|
||||||
|
}) {
|
||||||
const [tasks, setTasks] = useState<TaskView[]>([])
|
const [tasks, setTasks] = useState<TaskView[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
@@ -129,16 +143,18 @@ export function TasksTab({ token, webinarId }: { token: string; webinarId: numbe
|
|||||||
)}
|
)}
|
||||||
{!task.open && <span className="text-xs text-warn-600">zapisy zamknięte</span>}
|
{!task.open && <span className="text-xs text-warn-600">zapisy zamknięte</span>}
|
||||||
|
|
||||||
<Button
|
{!readOnly && (
|
||||||
type="button"
|
<Button
|
||||||
size="sm"
|
type="button"
|
||||||
variant={task.signedUp ? 'secondary' : 'primary'}
|
size="sm"
|
||||||
className="ml-auto"
|
variant={task.signedUp ? 'secondary' : 'primary'}
|
||||||
disabled={busy === task.id || (blocked && !task.signedUp)}
|
className="ml-auto"
|
||||||
onClick={() => toggle(task)}
|
disabled={busy === task.id || (blocked && !task.signedUp)}
|
||||||
>
|
onClick={() => toggle(task)}
|
||||||
{task.signedUp ? 'Wycofaj się' : task.full ? 'Brak miejsc' : 'Zgłaszam się'}
|
>
|
||||||
</Button>
|
{task.signedUp ? 'Wycofaj się' : task.full ? 'Brak miejsc' : 'Zgłaszam się'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{task.signedUp && (
|
{task.signedUp && (
|
||||||
|
|||||||
Reference in New Issue
Block a user