DEV Community

prem Prema
prem Prema

Posted on

RoughNote

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")));
}
Enter fullscreen mode Exit fullscreen mode

}

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();
}
Enter fullscreen mode Exit fullscreen mode

}


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;
}
Enter fullscreen mode Exit fullscreen mode

}


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";
    }
}
Enter fullscreen mode Exit fullscreen mode

}


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;
}
Enter fullscreen mode Exit fullscreen mode

}


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.");
}
Enter fullscreen mode Exit fullscreen mode

}


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);
Enter fullscreen mode Exit fullscreen mode

}


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);
Enter fullscreen mode Exit fullscreen mode

}


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);
Enter fullscreen mode Exit fullscreen mode

}


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);
Enter fullscreen mode Exit fullscreen mode

}


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());
}
Enter fullscreen mode Exit fullscreen mode

// 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);
}
Enter fullscreen mode Exit fullscreen mode

}


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);
}
Enter fullscreen mode Exit fullscreen mode

}


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);
}
Enter fullscreen mode Exit fullscreen mode

}


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);
}
Enter fullscreen mode Exit fullscreen mode

}


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));
}
Enter fullscreen mode Exit fullscreen mode

}


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));
}
Enter fullscreen mode Exit fullscreen mode

}


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));
}
Enter fullscreen mode Exit fullscreen mode

}


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());
}
Enter fullscreen mode Exit fullscreen mode

}


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));
}
Enter fullscreen mode Exit fullscreen mode

}

___________________________________________________________________# 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>
Enter fullscreen mode Exit fullscreen mode


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>
Enter fullscreen mode Exit fullscreen mode

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)