we are creating the Donation management app using springboot+postgres+react
This is Spring boot code
1_________________________________________________________
package com.maariyathaa.temple;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MaariyathaaApplication {
public static void main(String[] args) {
SpringApplication.run(MaariyathaaApplication.class, args);
}
}
2________________________________________________________
package com.maariyathaa.temple.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Maariyathaa Temple Donation Management API")
.version("1.0")
.description("API for managing donations, families, and volunteers for the Maariyathaa Temple")
.contact(new Contact()
.name("Maariyathaa Temple Support")
.email("support@maariyathaatemple.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0.html")));
}
}
3___________________________________________
package com.maariyathaa.temple.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig {
@bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/*")
.allowedOrigins("http://localhost:3000", "http://localhost:8080")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("")
.allowCredentials(true);
}
};
}
}
package com.maariyathaa.temple.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@data
@Entity
@Table(name = "donations")
public class Donation {
@id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "serial_no", unique = true)
private String serialNo;
@NotNull(message = "Donation date is mandatory")
@Column(name = "donation_date", nullable = false)
private LocalDate donationDate;
@NotNull(message = "Family is mandatory")
@ManyToOne
@JoinColumn(name = "family_id", nullable = false)
private Family family;
@ManyToOne
@JoinColumn(name = "volunteer_id")
private Volunteer volunteer;
@NotNull(message = "Amount is mandatory")
@Positive(message = "Amount must be positive")
@Column(nullable = false)
private BigDecimal amount;
@Column(name = "payment_type")
private String paymentType; // "CASH" or "TRANSFER"
@Column(name = "installment_number")
private Integer installmentNumber;
@Column(name = "total_installments")
private Integer totalInstallments;
@Column(name = "photo_url")
private String photoUrl;
@Column(name = "giver_sign_url")
private String giverSignUrl;
@Column(name = "volunteer_sign_url")
private String volunteerSignUrl;
@Column(name = "receiver_sign_url")
private String receiverSignUrl;
private String notes;
private String status;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// ... rest of the class remains the same
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
if (serialNo == null) {
serialNo = "DON" + System.currentTimeMillis();
}
if (status == null) {
status = "PENDING";
}
if (installmentNumber == null) {
installmentNumber = 1;
}
if (totalInstallments == null) {
totalInstallments = 1;
}
}
public Family getFamily() {
return family;
}
public void setFamily(Family family) {
this.family = family;
}
public Volunteer getVolunteer() {
return volunteer;
}
public void setVolunteer(Volunteer volunteer) {
this.volunteer = volunteer;
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
package com.maariyathaa.temple.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
@Entity
@Table(name = "families")
public class Family {
@id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Family name is mandatory")
@Column(nullable = false)
private String name;
@Column(name = "house_no")
private String houseNo;
@Column(name = "street_name")
private String streetName;
private String address;
@Pattern(regexp = "^[\\+]?[1-9][\\d]{0,15}$",
message = "Phone number format is invalid")
private String phone;
// Default constructor
public Family() {
}
// Old constructor (backward compatibility)
public Family(String name, String address, String phone) {
this.name = name;
this.address = address;
this.phone = phone;
}
// New constructor (all fields except id)
public Family(String name, String houseNo, String streetName, String address, String phone) {
this.name = name;
this.houseNo = houseNo;
this.streetName = streetName;
this.address = address;
this.phone = phone;
}
// Getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHouseNo() {
return houseNo;
}
public void setHouseNo(String houseNo) {
this.houseNo = houseNo;
}
public String getStreetName() {
return streetName;
}
public void setStreetName(String streetName) {
this.streetName = streetName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
package com.maariyathaa.temple.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDateTime;
@data
@Entity
@Table(name = "notifications")
public class Notification {
@id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull(message = "Family is mandatory")
@ManyToOne
@JoinColumn(name = "family_id", nullable = false)
private Family family;
@NotBlank(message = "Message is mandatory")
private String message;
@Column(name = "message_type")
private String messageType;
private String status;
@Column(name = "sent_at")
private LocalDateTime sentAt;
@Column(name = "created_at")
private LocalDateTime createdAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
if (status == null) {
status = "PENDING";
}
}
}
package com.maariyathaa.temple.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
@Entity
@Table(name = "volunteers")
public class Volunteer {
@id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Volunteer name is mandatory")
@Column(nullable = false)
private String name;
@Pattern(regexp = "^[\\+]?[1-9][\\d]{0,15}$", message = "Phone number format is invalid")
private String phone;
// Default constructor
public Volunteer() {
}
// Parameterized constructor
public Volunteer(String name, String phone) {
this.name = name;
this.phone = phone;
}
// Getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
package com.maariyathaa.temple.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
logger.warn("Resource not found: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<String> handleValidationException(ValidationException ex) {
logger.warn("Validation error: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
logger.error("Unexpected error occurred: ", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An unexpected error occurred. Please try again later.");
}
}
package com.maariyathaa.temple.exception;
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
package com.maariyathaa.temple.exception;
public class ValidationException extends RuntimeException {
public ValidationException(String message) {
super(message);
}
}
package com.maariyathaa.temple.repository;
import com.maariyathaa.temple.domain.Donation;
import com.maariyathaa.temple.domain.Family;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface DonationRepository extends JpaRepository {
List findByFamily(Family family);
List findByFamilyId(Long familyId);
// Pagination methods
Page<Donation> findByFamilyId(Long familyId, Pageable pageable);
Page<Donation> findAll(Pageable pageable);
// Additional pagination methods you might find useful
Page<Donation> findByStatus(String status, Pageable pageable);
@Query("SELECT d FROM Donation d WHERE d.family.id = :familyId AND d.status = :status")
Page<Donation> findByFamilyIdAndStatus(@Param("familyId") Long familyId,
@Param("status") String status,
Pageable pageable);
}
package com.maariyathaa.temple.repository;
import com.maariyathaa.temple.domain.Family;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface FamilyRepository extends JpaRepository {
// Basic pagination
Page findAll(Pageable pageable);
// Search with pagination
Page<Family> findByNameContainingIgnoreCase(String name, Pageable pageable);
@Query("SELECT f FROM Family f WHERE f.name LIKE %:searchTerm% OR f.address LIKE %:searchTerm%")
Page<Family> searchFamilies(@Param("searchTerm") String searchTerm, Pageable pageable);
}
package com.maariyathaa.temple.repository;
import com.maariyathaa.temple.domain.Notification;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface NotificationRepository extends JpaRepository {
// Basic pagination
Page findAll(Pageable pageable);
// Filter by status with pagination
Page<Notification> findByStatus(String status, Pageable pageable);
// Filter by family with pagination
Page<Notification> findByFamilyId(Long familyId, Pageable pageable);
@Query("SELECT n FROM Notification n WHERE n.family.id = :familyId AND n.status = :status")
Page<Notification> findByFamilyIdAndStatus(@Param("familyId") Long familyId,
@Param("status") String status,
Pageable pageable);
}
package com.maariyathaa.temple.repository;
import com.maariyathaa.temple.domain.Volunteer;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface VolunteerRepository extends JpaRepository {
// Basic pagination
Page findAll(Pageable pageable);
// Search with pagination
Page<Volunteer> findByNameContainingIgnoreCase(String name, Pageable pageable);
@Query("SELECT v FROM Volunteer v WHERE v.name LIKE %:searchTerm% OR v.phone LIKE %:searchTerm%")
Page<Volunteer> searchVolunteers(@Param("searchTerm") String searchTerm, Pageable pageable);
}
package com.maariyathaa.temple.service;
import com.maariyathaa.temple.domain.Donation;
import com.maariyathaa.temple.domain.Family;
import com.maariyathaa.temple.domain.Volunteer;
import com.maariyathaa.temple.exception.ResourceNotFoundException;
import com.maariyathaa.temple.repository.DonationRepository;
import com.maariyathaa.temple.repository.FamilyRepository;
import com.maariyathaa.temple.repository.VolunteerRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.HashMap;
@Service
public class DonationService {
private final DonationRepository donationRepository;
private final FamilyRepository familyRepository;
private final VolunteerRepository volunteerRepository;
private static final BigDecimal TARGET_AMOUNT = new BigDecimal("3000");
public DonationService(DonationRepository donationRepository, FamilyRepository familyRepository, VolunteerRepository volunteerRepository) {
this.donationRepository = donationRepository;
this.familyRepository = familyRepository;
this.volunteerRepository = volunteerRepository;
}
// Existing methods...
// Add these pagination methods:
public Page<Donation> getAllDonations(Pageable pageable) {
return donationRepository.findAll(pageable);
}
public Page<Donation> getDonationsByFamilyId(Long familyId, Pageable pageable) {
return donationRepository.findByFamilyId(familyId, pageable);
}
public Page<Donation> getDonationsByStatus(String status, Pageable pageable) {
return donationRepository.findByStatus(status, pageable);
}
// ... existing methods ...
public Donation saveDonation(Donation donation) {
// Validate family exists - USE ResourceNotFoundException
Family family = familyRepository.findById(donation.getFamily().getId())
.orElseThrow(() -> new ResourceNotFoundException("Family not found with id: " + donation.getFamily().getId()));
donation.setFamily(family); // This line stays the same
// Validate volunteer exists if provided - USE ResourceNotFoundException
if (donation.getVolunteer() != null && donation.getVolunteer().getId() != null) {
Volunteer volunteer = volunteerRepository.findById(donation.getVolunteer().getId())
.orElseThrow(() -> new ResourceNotFoundException("Volunteer not found with id: " + donation.getVolunteer().getId()));
donation.setVolunteer(volunteer);
} else {
donation.setVolunteer(null);
}
// Rest of the method remains exactly the same
// Get existing donations BEFORE saving the current one
List<Donation> familyDonations = donationRepository.findByFamilyId(family.getId());
// Calculate total paid by this family (excluding current donation)
BigDecimal totalPaid = familyDonations.stream()
.map(Donation::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// Add current donation amount to total
totalPaid = totalPaid.add(donation.getAmount());
// Determine payment status
if (totalPaid.compareTo(BigDecimal.ZERO) == 0) {
donation.setStatus("NOT_PAID");
} else if (totalPaid.compareTo(TARGET_AMOUNT) < 0) {
donation.setStatus("PARTIAL");
} else {
donation.setStatus("COMPLETED");
}
return donationRepository.save(donation);
}
public Map<String, Object> getFamilyPaymentStatus(Long familyId) {
Family family = familyRepository.findById(familyId)
.orElseThrow(() -> new RuntimeException("Family not found with id: " + familyId));
List<Donation> donations = donationRepository.findByFamilyId(familyId);
BigDecimal totalPaid = donations.stream()
.map(Donation::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal remainingAmount = TARGET_AMOUNT.subtract(totalPaid);
if (remainingAmount.compareTo(BigDecimal.ZERO) < 0) {
remainingAmount = BigDecimal.ZERO;
}
String status;
if (totalPaid.compareTo(BigDecimal.ZERO) == 0) {
status = "NOT_PAID";
} else if (totalPaid.compareTo(TARGET_AMOUNT) < 0) {
status = "PARTIAL";
} else {
status = "COMPLETED";
}
Map<String, Object> statusInfo = new HashMap<>();
statusInfo.put("familyId", familyId);
statusInfo.put("familyName", family.getName());
statusInfo.put("totalPaid", totalPaid);
statusInfo.put("targetAmount", TARGET_AMOUNT);
statusInfo.put("remainingAmount", remainingAmount);
statusInfo.put("status", status);
statusInfo.put("installments", donations.size());
statusInfo.put("donations", donations);
return statusInfo;
}
public Map<Long, Map<String, Object>> getAllFamiliesPaymentStatus() {
List<Family> families = familyRepository.findAll();
Map<Long, Map<String, Object>> statusMap = new HashMap<>();
for (Family family : families) {
statusMap.put(family.getId(), getFamilyPaymentStatus(family.getId()));
}
return statusMap;
}
public List<Donation> getPendingPayments() {
List<Donation> allDonations = donationRepository.findAll();
// Filter families that haven't completed payment
return allDonations.stream()
.filter(donation -> {
List<Donation> familyDonations = donationRepository.findByFamilyId(donation.getFamily().getId());
BigDecimal totalPaid = familyDonations.stream()
.map(Donation::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return totalPaid.compareTo(TARGET_AMOUNT) < 0;
})
.collect(Collectors.toList());
}
// Add these to DonationService
public List getAllDonations() {
return donationRepository.findAll();
}
public Donation getDonationById(Long id) {
return donationRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Donation not found with id: " + id));
}
public List<Donation> getDonationsByFamilyId(Long familyId) {
return donationRepository.findByFamilyId(familyId);
}
public void deleteDonation(Long id) {
donationRepository.deleteById(id);
}
}
package com.maariyathaa.temple.service;
import com.maariyathaa.temple.domain.Family;
import com.maariyathaa.temple.repository.FamilyRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class FamilyService {
private final FamilyRepository familyRepository;
public FamilyService(FamilyRepository familyRepository) {
this.familyRepository = familyRepository;
}
// Add pagination methods:
public Page<Family> getAllFamilies(Pageable pageable) {
return familyRepository.findAll(pageable);
}
public Page<Family> searchFamilies(String searchTerm, Pageable pageable) {
return familyRepository.searchFamilies(searchTerm, pageable);
}
// Keep existing methods:
public List<Family> getAllFamilies() {
return familyRepository.findAll();
}
public Family getFamilyById(Long id) {
return familyRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Family not found with id: " + id));
}
public Family saveFamily(Family family) {
return familyRepository.save(family);
}
public void deleteFamily(Long id) {
familyRepository.deleteById(id);
}
}
package com.maariyathaa.temple.service;
import com.maariyathaa.temple.domain.Notification;
import com.maariyathaa.temple.repository.NotificationRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class NotificationService {
private final NotificationRepository notificationRepository;
public NotificationService(NotificationRepository notificationRepository) {
this.notificationRepository = notificationRepository;
}
// Pagination methods:
public Page<Notification> getAllNotifications(Pageable pageable) {
return notificationRepository.findAll(pageable);
}
public Page<Notification> getNotificationsByStatus(String status, Pageable pageable) {
return notificationRepository.findByStatus(status, pageable);
}
public Page<Notification> getNotificationsByFamilyId(Long familyId, Pageable pageable) {
return notificationRepository.findByFamilyId(familyId, pageable);
}
// Regular methods:
public List<Notification> getAllNotifications() {
return notificationRepository.findAll();
}
public Notification getNotificationById(Long id) {
return notificationRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Notification not found with id: " + id));
}
public Notification saveNotification(Notification notification) {
return notificationRepository.save(notification);
}
public void deleteNotification(Long id) {
notificationRepository.deleteById(id);
}
}
package com.maariyathaa.temple.service;
import com.maariyathaa.temple.domain.Volunteer;
import com.maariyathaa.temple.repository.VolunteerRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class VolunteerService {
private final VolunteerRepository volunteerRepository;
public VolunteerService(VolunteerRepository volunteerRepository) {
this.volunteerRepository = volunteerRepository;
}
// Add pagination methods:
public Page<Volunteer> getAllVolunteers(Pageable pageable) {
return volunteerRepository.findAll(pageable);
}
public Page<Volunteer> searchVolunteers(String searchTerm, Pageable pageable) {
return volunteerRepository.searchVolunteers(searchTerm, pageable);
}
// Keep existing methods:
public List<Volunteer> getAllVolunteers() {
return volunteerRepository.findAll();
}
public Volunteer getVolunteerById(Long id) {
return volunteerRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Volunteer not found with id: " + id));
}
public Volunteer saveVolunteer(Volunteer volunteer) {
return volunteerRepository.save(volunteer);
}
public void deleteVolunteer(Long id) {
volunteerRepository.deleteById(id);
}
}
package com.maariyathaa.temple.web;
import com.maariyathaa.temple.domain.Donation;
import com.maariyathaa.temple.service.DonationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/donations")
@RequiredArgsConstructor
public class DonationController {
private final DonationService donationService;
@Operation(summary = "Get all donations with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Donations retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping
public ResponseEntity<Page<Donation>> getAllDonations(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "donationDate") String sortBy,
@RequestParam(defaultValue = "desc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
return ResponseEntity.ok(donationService.getAllDonations(pageable));
}
@Operation(summary = "Get donation by ID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Donation retrieved successfully"),
@ApiResponse(responseCode = "404", description = "Donation not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/{id}")
public ResponseEntity<Donation> getDonationById(@PathVariable Long id) {
return ResponseEntity.ok(donationService.getDonationById(id));
}
@Operation(summary = "Create a new donation")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Donation created successfully"),
@ApiResponse(responseCode = "400", description = "Invalid input"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@PostMapping
public ResponseEntity<Donation> createDonation(@Valid @RequestBody Donation donation) {
return new ResponseEntity<>(donationService.saveDonation(donation), HttpStatus.CREATED);
}
@Operation(summary = "Delete a donation")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Donation deleted successfully"),
@ApiResponse(responseCode = "404", description = "Donation not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteDonation(@PathVariable Long id) {
donationService.deleteDonation(id);
return ResponseEntity.noContent().build();
}
@Operation(summary = "Get donations by family ID with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Donations retrieved successfully"),
@ApiResponse(responseCode = "404", description = "Family not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/family/{familyId}")
public ResponseEntity<Page<Donation>> getDonationsByFamily(
@PathVariable Long familyId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "donationDate") String sortBy,
@RequestParam(defaultValue = "desc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
return ResponseEntity.ok(donationService.getDonationsByFamilyId(familyId, pageable));
}
@Operation(summary = "Get donations by status with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Donations retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/status/{status}")
public ResponseEntity<Page<Donation>> getDonationsByStatus(
@PathVariable String status,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "donationDate") String sortBy,
@RequestParam(defaultValue = "desc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
return ResponseEntity.ok(donationService.getDonationsByStatus(status, pageable));
}
}
package com.maariyathaa.temple.web;
import com.maariyathaa.temple.domain.Family;
import com.maariyathaa.temple.service.FamilyService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/families")
@RequiredArgsConstructor
public class FamilyController {
private final FamilyService familyService;
@Operation(summary = "Get all families with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Families retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping
public ResponseEntity<Page<Family>> getAllFamilies(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "asc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
return ResponseEntity.ok(familyService.getAllFamilies(pageable));
}
@Operation(summary = "Get family by ID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Family retrieved successfully"),
@ApiResponse(responseCode = "404", description = "Family not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/{id}")
public ResponseEntity<Family> getFamilyById(@PathVariable Long id) {
return ResponseEntity.ok(familyService.getFamilyById(id));
}
@Operation(summary = "Create a new family")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Family created successfully"),
@ApiResponse(responseCode = "400", description = "Invalid input"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@PostMapping
public ResponseEntity<Family> createFamily(@Valid @RequestBody Family family) {
return new ResponseEntity<>(familyService.saveFamily(family), HttpStatus.CREATED);
}
@Operation(summary = "Delete a family")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Family deleted successfully"),
@ApiResponse(responseCode = "404", description = "Family not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteFamily(@PathVariable Long id) {
familyService.deleteFamily(id);
return ResponseEntity.noContent().build();
}
@Operation(summary = "Search families with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Families retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/search")
public ResponseEntity<Page<Family>> searchFamilies(
@RequestParam String searchTerm,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "asc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
return ResponseEntity.ok(familyService.searchFamilies(searchTerm, pageable));
}
}
package com.maariyathaa.temple.web;
import com.maariyathaa.temple.domain.Notification;
import com.maariyathaa.temple.service.NotificationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/notifications")
@RequiredArgsConstructor
public class NotificationController {
private final NotificationService notificationService;
@Operation(summary = "Get all notifications with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Notifications retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping
public ResponseEntity<Page<Notification>> getAllNotifications(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "createdAt") String sortBy,
@RequestParam(defaultValue = "desc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
return ResponseEntity.ok(notificationService.getAllNotifications(pageable));
}
@Operation(summary = "Get notifications by status with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Notifications retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/status/{status}")
public ResponseEntity<Page<Notification>> getNotificationsByStatus(
@PathVariable String status,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "createdAt") String sortBy,
@RequestParam(defaultValue = "desc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
return ResponseEntity.ok(notificationService.getNotificationsByStatus(status, pageable));
}
@Operation(summary = "Get notifications by family ID with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Notifications retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/family/{familyId}")
public ResponseEntity<Page<Notification>> getNotificationsByFamilyId(
@PathVariable Long familyId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "createdAt") String sortBy,
@RequestParam(defaultValue = "desc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
return ResponseEntity.ok(notificationService.getNotificationsByFamilyId(familyId, pageable));
}
}
package com.maariyathaa.temple.web;
import com.maariyathaa.temple.service.DonationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/payments")
@RequiredArgsConstructor
public class PaymentController {
private final DonationService donationService;
@Operation(summary = "Get payment status for a family")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Payment status retrieved successfully"),
@ApiResponse(responseCode = "404", description = "Family not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/status/family/{familyId}")
public ResponseEntity<Map<String, Object>> getFamilyPaymentStatus(@PathVariable Long familyId) {
return ResponseEntity.ok(donationService.getFamilyPaymentStatus(familyId));
}
@Operation(summary = "Get payment status for all families")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Payment statuses retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/status/all")
public ResponseEntity<Map<Long, Map<String, Object>>> getAllFamiliesPaymentStatus() {
return ResponseEntity.ok(donationService.getAllFamiliesPaymentStatus());
}
@Operation(summary = "Get pending payments")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Pending payments retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/pending")
public ResponseEntity<?> getPendingPayments() {
return ResponseEntity.ok(donationService.getPendingPayments());
}
}
package com.maariyathaa.temple.web;
import com.maariyathaa.temple.domain.Volunteer;
import com.maariyathaa.temple.service.VolunteerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/volunteers")
@RequiredArgsConstructor
public class VolunteerController {
private final VolunteerService volunteerService;
@Operation(summary = "Get all volunteers with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Volunteers retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping
public ResponseEntity<Page<Volunteer>> getAllVolunteers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "asc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
return ResponseEntity.ok(volunteerService.getAllVolunteers(pageable));
}
@Operation(summary = "Get volunteer by ID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Volunteer retrieved successfully"),
@ApiResponse(responseCode = "404", description = "Volunteer not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/{id}")
public ResponseEntity<Volunteer> getVolunteerById(@PathVariable Long id) {
return ResponseEntity.ok(volunteerService.getVolunteerById(id));
}
@Operation(summary = "Create a new volunteer")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Volunteer created successfully"),
@ApiResponse(responseCode = "400", description = "Invalid input"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@PostMapping
public ResponseEntity<Volunteer> createVolunteer(@Valid @RequestBody Volunteer volunteer) {
return new ResponseEntity<>(volunteerService.saveVolunteer(volunteer), HttpStatus.CREATED);
}
@Operation(summary = "Delete a volunteer")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Volunteer deleted successfully"),
@ApiResponse(responseCode = "404", description = "Volunteer not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteVolunteer(@PathVariable Long id) {
volunteerService.deleteVolunteer(id);
return ResponseEntity.noContent().build();
}
@Operation(summary = "Search volunteers with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Volunteers retrieved successfully"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/search")
public ResponseEntity<Page<Volunteer>> searchVolunteers(
@RequestParam String searchTerm,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "asc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction)
? Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
return ResponseEntity.ok(volunteerService.searchVolunteers(searchTerm, pageable));
}
}
___________________________________________________________________# Application name
spring.application.name=Maariyathaa
Database configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/maariyathaa
spring.datasource.username=maariyathaa
spring.datasource.password=maariyathaa
JPA configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
Server port
server.port=8080
Better JSON output
spring.jackson.serialization.indent-output=true
<?xml version="1.0" encoding="UTF-8"?>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
org.springframework.boot
spring-boot-starter-parent
3.5.5
<!-- lookup parent from repository -->
com.maariyathaa
maariyathaa
0.0.1-SNAPSHOT
Maariyathaa
Temple donation management system
17
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-web
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
org.springdoc
springdoc-openapi-starter-webmvc-ui
2.8.5
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
FrontEnd Section
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './App.css';
function App() {
const [families, setFamilies] = useState([]);
const [volunteers, setVolunteers] = useState([]);
const [activeTab, setActiveTab] = useState('families');
const [newFamily, setNewFamily] = useState({ name: '', address: '', phone: '' });
const [newVolunteer, setNewVolunteer] = useState({ name: '', phone: '' });
const [editingFamily, setEditingFamily] = useState(null);
const [editingVolunteer, setEditingVolunteer] = useState(null);
const [editFamilyData, setEditFamilyData] = useState({ name: '', address: '', phone: '' });
const [editVolunteerData, setEditVolunteerData] = useState({ name: '', phone: '' });
// Fetch data from backend
useEffect(() => {
fetchFamilies();
fetchVolunteers();
}, []);
const fetchFamilies = async () => {
try {
const response = await axios.get('/api/families');
setFamilies(response.data);
} catch (error) {
console.error('Error fetching families:', error);
}
};
const fetchVolunteers = async () => {
try {
const response = await axios.get('/api/volunteers');
setVolunteers(response.data);
} catch (error) {
console.error('Error fetching volunteers:', error);
}
};
const handleAddFamily = async (e) => {
e.preventDefault();
try {
await axios.post('/api/families', newFamily);
setNewFamily({ name: '', address: '', phone: '' });
fetchFamilies();
} catch (error) {
console.error('Error adding family:', error);
}
};
const handleAddVolunteer = async (e) => {
e.preventDefault();
try {
await axios.post('/api/volunteers', newVolunteer);
setNewVolunteer({ name: '', phone: '' });
fetchVolunteers();
} catch (error) {
console.error('Error adding volunteer:', error);
}
};
// ===== ADD EDIT AND DELETE FUNCTIONALITY HERE =====
// Family functions
const handleEditFamily = (family) => {
setEditingFamily(family.id);
setEditFamilyData({
name: family.name,
address: family.address,
phone: family.phone
});
};
const handleUpdateFamily = async (id) => {
try {
await axios.put(/api/families/${id}
, editFamilyData);
setEditingFamily(null);
fetchFamilies();
} catch (error) {
console.error('Error updating family:', error);
}
};
const handleDeleteFamily = async (id) => {
try {
await axios.delete(/api/families/${id}
);
fetchFamilies();
} catch (error) {
console.error('Error deleting family:', error);
}
};
// Volunteer functions
const handleEditVolunteer = (volunteer) => {
setEditingVolunteer(volunteer.id);
setEditVolunteerData({
name: volunteer.name,
phone: volunteer.phone
});
};
const handleUpdateVolunteer = async (id) => {
try {
await axios.put(/api/volunteers/${id}
, editVolunteerData);
setEditingVolunteer(null);
fetchVolunteers();
} catch (error) {
console.error('Error updating volunteer:', error);
}
};
const handleDeleteVolunteer = async (id) => {
try {
await axios.delete(/api/volunteers/${id}
);
fetchVolunteers();
} catch (error) {
console.error('Error deleting volunteer:', error);
}
};
// ===== END OF EDIT AND DELETE FUNCTIONALITY =====
return (
Maariyathaa Temple Management System
<div className="tabs">
<button
className={activeTab === 'families' ? 'active' : ''}
onClick={() => setActiveTab('families')}
>
Families
</button>
<button
className={activeTab === 'volunteers' ? 'active' : ''}
onClick={() => setActiveTab('volunteers')}
>
Volunteers
</button>
</div>
<div className="content">
{activeTab === 'families' && (
<div>
<h2>Families</h2>
<form onSubmit={handleAddFamily} className="form">
<h3>Add New Family</h3>
<input
type="text"
placeholder="Family Name"
value={newFamily.name}
onChange={(e) => setNewFamily({...newFamily, name: e.target.value})}
required
/>
<input
type="text"
placeholder="Address"
value={newFamily.address}
onChange={(e) => setNewFamily({...newFamily, address: e.target.value})}
/>
<input
type="text"
placeholder="Phone"
value={newFamily.phone}
onChange={(e) => setNewFamily({...newFamily, phone: e.target.value})}
/>
<button type="submit">Add Family</button>
</form>
<div className="list">
<h3>Family List</h3>
{families.length === 0 ? (
<p>No families found</p>
) : (
<ul>
{families.map(family => (
<li key={family.id}>
{editingFamily === family.id ? (
<div className="edit-form">
<input
type="text"
value={editFamilyData.name}
onChange={(e) => setEditFamilyData({...editFamilyData, name: e.target.value})}
/>
<input
type="text"
value={editFamilyData.address}
onChange={(e) => setEditFamilyData({...editFamilyData, address: e.target.value})}
/>
<input
type="text"
value={editFamilyData.phone}
onChange={(e) => setEditFamilyData({...editFamilyData, phone: e.target.value})}
/>
<button onClick={() => handleUpdateFamily(family.id)}>Save</button>
<button onClick={() => setEditingFamily(null)}>Cancel</button>
</div>
) : (
<div>
<strong>{family.name}</strong><br />
{family.address && <span>Address: {family.address}<br /></span>}
{family.phone && <span>Phone: {family.phone}</span>}
<div className="item-actions">
<button onClick={() => handleEditFamily(family)}>Edit</button>
<button onClick={() => handleDeleteFamily(family.id)}>Delete</button>
</div>
</div>
)}
</li>
))}
</ul>
)}
</div>
</div>
)}
{activeTab === 'volunteers' && (
<div>
<h2>Volunteers</h2>
<form onSubmit={handleAddVolunteer} className="form">
<h3>Add New Volunteer</h3>
<input
type="text"
placeholder="Volunteer Name"
value={newVolunteer.name}
onChange={(e) => setNewVolunteer({...newVolunteer, name: e.target.value})}
required
/>
<input
type="text"
placeholder="Phone"
value={newVolunteer.phone}
onChange={(e) => setNewVolunteer({...newVolunteer, phone: e.target.value})}
/>
<button type="submit">Add Volunteer</button>
</form>
<div className="list">
<h3>Volunteer List</h3>
{volunteers.length === 0 ? (
<p>No volunteers found</p>
) : (
<ul>
{volunteers.map(volunteer => (
<li key={volunteer.id}>
{editingVolunteer === volunteer.id ? (
<div className="edit-form">
<input
type="text"
value={editVolunteerData.name}
onChange={(e) => setEditVolunteerData({...editVolunteerData, name: e.target.value})}
/>
<input
type="text"
value={editVolunteerData.phone}
onChange={(e) => setEditVolunteerData({...editVolunteerData, phone: e.target.value})}
/>
<button onClick={() => handleUpdateVolunteer(volunteer.id)}>Save</button>
<button onClick={() => setEditingVolunteer(null)}>Cancel</button>
</div>
) : (
<div>
<strong>{volunteer.name}</strong><br />
{volunteer.phone && <span>Phone: {volunteer.phone}</span>}
<div className="item-actions">
<button onClick={() => handleEditVolunteer(volunteer)}>Edit</button>
<button onClick={() => handleDeleteVolunteer(volunteer.id)}>Delete</button>
</div>
</div>
)}
</li>
))}
</ul>
)}
</div>
</div>
)}
</div>
</div>
);
}
export default App;
/* Donation Management Styles */
.donation-management {
padding: 20px;
}
.donation-stats {
display: flex;
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background-color: #f5f5f5;
padding: 20px;
border-radius: 8px;
text-align: center;
flex: 1;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stat-card h3 {
margin: 0 0 10px 0;
color: #666;
font-size: 14px;
}
.stat-card p {
margin: 0;
font-size: 24px;
font-weight: bold;
color: #333;
}
.donation-form {
background-color: #f9f9f9;
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 15px;
}
.form-group {
flex: 1;
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.installment-fields {
display: flex;
align-items: center;
gap: 10px;
}
.installment-fields input {
width: 60px;
}
.installment-fields span {
color: #666;
}
.btn-primary {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn-primary:hover {
background-color: #45a049;
}
.donation-list table,
.payment-status table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
}
.donation-list th,
.donation-list td,
.payment-status th,
.payment-status td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}
.donation-list th,
.payment-status th {
background-color: #f2f2f2;
}
.status-pending {
color: #ff9800;
font-weight: bold;
}
.status-partial {
color: #2196f3;
font-weight: bold;
}
.status-completed {
color: #4CAF50;
font-weight: bold;
}
.status-not-paid {
color: #f44336;
font-weight: bold;
}
.status-fully-paid {
color: #4CAF50;
font-weight: bold;
}
.action-buttons {
display: flex;
gap: 5px;
}
.btn-edit {
background-color: #2196F3;
color: white;
padding: 5px 10px;
border: none;
border-radius: 3px;
cursor: pointer;
}
.btn-delete {
background-color: #f44336;
color: white;
padding: 5px 10px;
border: none;
border-radius: 3px;
cursor: pointer;
}
/* Payment Status Styles */
.payment-overview {
margin-bottom: 30px;
}
.status-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.status-card {
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.status-title {
font-size: 14px;
color: #6c757d;
margin-bottom: 10px;
}
.status-value {
font-size: 24px;
font-weight: bold;
color: #343a40;
}
.status-badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.status-not-paid {
background-color: #ffeaea;
color: #dc3545;
}
.status-partial {
background-color: #fff3cd;
color: #856404;
}
.status-completed {
background-color: #d4edda;
color: #155724;
}
.status-pending {
background-color: #cce5ff;
color: #004085;
}
.payment-status-table {
margin-bottom: 30px;
}
.payment-status-table table {
width: 100%;
border-collapse: collapse;
}
.payment-status-table th,
.payment-status-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #dee2e6;
}
.payment-status-table th {
background-color: #f8f9fa;
font-weight: bold;
}
.btn-reminder {
background-color: #17a2b8;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.btn-reminder:hover:not(:disabled) {
background-color: #138496;
}
.btn-reminder:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.installment-history {
margin-bottom: 30px;
}
.installment-history table {
width: 100%;
border-collapse: collapse;
}
.installment-history th,
.installment-history td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #dee2e6;
}
.installment-history th {
background-color: #f8f9fa;
font-weight: bold;
}
/* Add these styles to your existing App.css */
.item-actions {
margin-top: 10px;
}
.item-actions button {
margin-right: 5px;
padding: 5px 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
.item-actions button:last-child {
background-color: #f44336;
}
.edit-form {
display: flex;
flex-direction: column;
gap: 5px;
}
.edit-form input {
padding: 5px;
border: 1px solid #ddd;
border-radius: 3px;
}
.edit-form button {
padding: 5px 10px;
margin-right: 5px;
border: none;
border-radius: 3px;
cursor: pointer;
}
.edit-form button:first-of-type {
background-color: #4CAF50;
color: white;
}
.edit-form button:last-of-type {
background-color: #f44336;
color: white;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 50vh;
}
.loading-container p {
margin-top: 20px;
font-size: 18px;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 50vh;
}
.loading-container p {
margin-top: 20px;
font-size: 18px;
}
const fetchData = async () => {
try {
const familyRes = await axios.get("/api/families");
console.log("📦 Families raw data:", familyRes.data);
// ✅ If API returns { content: [...] }
const familyData = Array.isArray(familyRes.data)
? familyRes.data
: familyRes.data.content || [];
setFamilies(familyData);
const donationRes = await axios.get("/api/donations");
console.log("📦 Donations raw data:", donationRes.data);
const donationData = Array.isArray(donationRes.data)
? donationRes.data
: donationRes.data.content || [];
setDonations(donationData);
const volunteerRes = await axios.get("/api/volunteers");
console.log("📦 Volunteers raw data:", volunteerRes.data);
const volunteerData = Array.isArray(volunteerRes.data)
? volunteerRes.data
: volunteerRes.data.content || [];
setVolunteers(volunteerData);
const paymentRes = await axios.get("/api/payments");
console.log("📦 Payments raw data:", paymentRes.data);
const paymentData = Array.isArray(paymentRes.data)
? paymentRes.data
: paymentRes.data.content || [];
setPayments(paymentData);
} catch (error) {
console.error("❌ Error fetching data:", error);
}
};
*The spring boot run result *
. ____ _ __ _ _
/\ / ' __ _ ()_ __ __ _ \ \ \ \
( ( )_ | '_ | '| | ' \/ ` | \ \ \ \
\/ _)| |)| | | | | || (| | ) ) ) )
' |__| .|| ||| |_, | / / / /
=========||==============|_/=////
[32m :: Spring Boot :: [39m [2m (v3.5.5)[0;39m
[2m2025-08-25T18:52:37.694+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mc.m.temple.MaariyathaaApplication [0;39m [2m:[0;39m Starting MaariyathaaApplication using Java 17.0.16 with PID 27963 (/home/prem/Developer/sts/Maariyathaa/target/classes started by prem in /home/prem/Developer/sts/Maariyathaa)
[2m2025-08-25T18:52:37.696+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mc.m.temple.MaariyathaaApplication [0;39m [2m:[0;39m No active profile set, falling back to 1 default profile: "default"
[2m2025-08-25T18:52:37.751+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36m.e.DevToolsPropertyDefaultsPostProcessor[0;39m [2m:[0;39m Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
[2m2025-08-25T18:52:37.751+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36m.e.DevToolsPropertyDefaultsPostProcessor[0;39m [2m:[0;39m For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
[2m2025-08-25T18:52:38.737+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Bootstrapping Spring Data JPA repositories in DEFAULT mode.
[2m2025-08-25T18:52:38.797+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Finished Spring Data repository scanning in 48 ms. Found 4 JPA repository interfaces.
[2m2025-08-25T18:52:39.327+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.s.b.w.embedded.tomcat.TomcatWebServer [0;39m [2m:[0;39m Tomcat initialized with port 8080 (http)
[2m2025-08-25T18:52:39.342+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.apache.catalina.core.StandardService [0;39m [2m:[0;39m Starting service [Tomcat]
[2m2025-08-25T18:52:39.342+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.apache.catalina.core.StandardEngine [0;39m [2m:[0;39m Starting Servlet engine: [Apache Tomcat/10.1.44]
[2m2025-08-25T18:52:39.382+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.a.c.c.C.[Tomcat].[localhost].[/] [0;39m [2m:[0;39m Initializing Spring embedded WebApplicationContext
[2m2025-08-25T18:52:39.383+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mw.s.c.ServletWebServerApplicationContext[0;39m [2m:[0;39m Root WebApplicationContext: initialization completed in 1631 ms
[2m2025-08-25T18:52:39.509+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.hibernate.jpa.internal.util.LogHelper [0;39m [2m:[0;39m HHH000204: Processing PersistenceUnitInfo [name: default]
[2m2025-08-25T18:52:39.567+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36morg.hibernate.Version [0;39m [2m:[0;39m HHH000412: Hibernate ORM core version 6.6.26.Final
[2m2025-08-25T18:52:39.603+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.h.c.internal.RegionFactoryInitiator [0;39m [2m:[0;39m HHH000026: Second-level cache disabled
[2m2025-08-25T18:52:39.829+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.s.o.j.p.SpringPersistenceUnitInfo [0;39m [2m:[0;39m No LoadTimeWeaver setup: ignoring JPA class transformer
[2m2025-08-25T18:52:39.862+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-1 - Starting...
[2m2025-08-25T18:52:40.138+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mcom.zaxxer.hikari.pool.HikariPool [0;39m [2m:[0;39m HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@1c42865c
[2m2025-08-25T18:52:40.139+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-1 - Start completed.
[2m2025-08-25T18:52:40.175+05:30[0;39m [33m WARN[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36morg.hibernate.orm.deprecation [0;39m [2m:[0;39m HHH90000025: PostgreSQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
[2m2025-08-25T18:52:40.192+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36morg.hibernate.orm.connections.pooling [0;39m [2m:[0;39m HHH10001005: Database info:
Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
Database driver: undefined/unknown
Database version: 14.18
Autocommit mode: undefined/unknown
Isolation level: undefined/unknown
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
[2m2025-08-25T18:52:40.946+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.h.e.t.j.p.i.JtaPlatformInitiator [0;39m [2m:[0;39m HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
[2m2025-08-25T18:52:41.177+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mj.LocalContainerEntityManagerFactoryBean[0;39m [2m:[0;39m Initialized JPA EntityManagerFactory for persistence unit 'default'
[2m2025-08-25T18:52:41.417+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.s.d.j.r.query.QueryEnhancerFactory [0;39m [2m:[0;39m Hibernate is in classpath; If applicable, HQL parser will be used.
[2m2025-08-25T18:52:41.944+05:30[0;39m [33m WARN[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mJpaBaseConfiguration$JpaWebConfiguration[0;39m [2m:[0;39m spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
[2m2025-08-25T18:52:42.288+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.s.b.d.a.OptionalLiveReloadServer [0;39m [2m:[0;39m LiveReload server is running on port 35729
[2m2025-08-25T18:52:42.328+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mo.s.b.w.embedded.tomcat.TomcatWebServer [0;39m [2m:[0;39m Tomcat started on port 8080 (http) with context path '/'
[2m2025-08-25T18:52:42.339+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [ restartedMain] [0;39m[36mc.m.temple.MaariyathaaApplication [0;39m [2m:[0;39m Started MaariyathaaApplication in 5.131 seconds (process running for 5.803)
[2m2025-08-25T18:52:54.225+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [nio-8080-exec-1] [0;39m[36mo.a.c.c.C.[Tomcat].[localhost].[/] [0;39m [2m:[0;39m Initializing Spring DispatcherServlet 'dispatcherServlet'
[2m2025-08-25T18:52:54.226+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [nio-8080-exec-1] [0;39m[36mo.s.web.servlet.DispatcherServlet [0;39m [2m:[0;39m Initializing Servlet 'dispatcherServlet'
[2m2025-08-25T18:52:54.229+05:30[0;39m [32m INFO[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [nio-8080-exec-1] [0;39m[36mo.s.web.servlet.DispatcherServlet [0;39m [2m:[0;39m Completed initialization in 3 ms
Hibernate:
select
v1_0.id,
v1_0.name,
v1_0.phone
from
volunteers v1_0
order by
v1_0.name
offset
? rows
fetch
first ? rows only
Hibernate:
select
f1_0.id,
f1_0.address,
f1_0.house_no,
f1_0.name,
f1_0.phone,
f1_0.street_name
from
families f1_0
order by
f1_0.name
offset
? rows
fetch
first ? rows only
[2m2025-08-25T18:52:54.512+05:30[0;39m [33m WARN[0;39m [35m27963[0;39m [2m--- [Maariyathaa] [nio-8080-exec-1] [0;39m[36mration$PageModule$WarningLoggingModifier[0;39m [2m:[0;39m Serializing PageImpl instances as-is is not supported, meaning that there is no guarantee about the stability of the resulting JSON structure!
For a stable JSON structure, please use Spring Data's PagedModel (globally via @EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO))
or Spring HATEOAS and Spring Data's PagedResourcesAssembler as documented in https://docs.spring.io/spring-data/commons/reference/repositories/core-extensions.html#core.web.pageables.
Hibernate:
select
f1_0.id,
f1_0.address,
f1_0.house_no,
f1_0.name,
f1_0.phone,
f1_0.street_name
from
families f1_0
order by
f1_0.name
offset
? rows
fetch
first ? rows only
Hibernate:
select
v1_0.id,
v1_0.name,
v1_0.phone
from
volunteers v1_0
order by
v1_0.name
offset
? rows
fetch
first ? rows only
Top comments (0)