CSSの最適化や内部挙動の改良
This commit is contained in:
@@ -76,13 +76,19 @@ func Connect(dbConfig config.DatabaseConfig, debug bool) error {
|
||||
}
|
||||
|
||||
func Migrate() error {
|
||||
return DB.AutoMigrate(
|
||||
if err := DB.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.Assignment{},
|
||||
&models.RecurringAssignment{},
|
||||
&models.APIKey{},
|
||||
&models.UserNotificationSettings{},
|
||||
)
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return DB.Model(&models.RecurringAssignment{}).
|
||||
Where("recurrence_type = ? AND generation_lead_days = 0", models.RecurrenceWeekly).
|
||||
Update("generation_lead_days", 7).Error
|
||||
}
|
||||
|
||||
func GetDB() *gorm.DB {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -108,6 +109,8 @@ func (h *AssignmentHandler) New(c *gin.Context) {
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
now := time.Now()
|
||||
tomorrow := now.AddDate(0, 0, 1)
|
||||
defaultDue := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 23, 59, 0, 0, now.Location())
|
||||
|
||||
RenderHTML(c, http.StatusOK, "assignments/new.html", gin.H{
|
||||
"title": "課題登録",
|
||||
@@ -115,6 +118,7 @@ func (h *AssignmentHandler) New(c *gin.Context) {
|
||||
"userName": name,
|
||||
"currentWeekday": int(now.Weekday()),
|
||||
"currentDay": now.Day(),
|
||||
"defaultDueDate": defaultDue.Format("2006-01-02T15:04"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -217,6 +221,12 @@ func (h *AssignmentHandler) Create(c *gin.Context) {
|
||||
|
||||
dueTime := dueDate.Format("15:04")
|
||||
|
||||
generationLeadDays := 0
|
||||
if v, err := strconv.Atoi(c.PostForm("generation_lead_days")); err == nil && v >= 0 {
|
||||
generationLeadDays = v
|
||||
}
|
||||
generationLeadTime := c.PostForm("generation_lead_time")
|
||||
|
||||
recurringService := service.NewRecurringAssignmentService()
|
||||
input := service.CreateRecurringAssignmentInput{
|
||||
Title: title,
|
||||
@@ -233,6 +243,8 @@ func (h *AssignmentHandler) Create(c *gin.Context) {
|
||||
EndDate: endDate,
|
||||
ReminderEnabled: reminderEnabled,
|
||||
UrgentReminderEnabled: urgentReminderEnabled,
|
||||
GenerationLeadDays: generationLeadDays,
|
||||
GenerationLeadTime: generationLeadTime,
|
||||
FirstDueDate: dueDate,
|
||||
}
|
||||
|
||||
@@ -353,7 +365,10 @@ func (h *AssignmentHandler) Toggle(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
|
||||
h.assignmentService.ToggleComplete(userID, uint(id))
|
||||
assignment, err := h.assignmentService.ToggleComplete(userID, uint(id))
|
||||
if err == nil && assignment.IsCompleted && assignment.RecurringAssignmentID != nil {
|
||||
h.recurringService.TriggerForRecurring(*assignment.RecurringAssignmentID)
|
||||
}
|
||||
|
||||
referer := c.Request.Referer()
|
||||
if referer == "" {
|
||||
@@ -459,6 +474,69 @@ func (h *AssignmentHandler) UnarchiveSubject(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, "/statistics?include_archived=true")
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) ExportCSV(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
|
||||
var from, to *time.Time
|
||||
if fromStr := c.Query("from"); fromStr != "" {
|
||||
if t, err := time.ParseInLocation("2006-01-02", fromStr, time.Local); err == nil {
|
||||
from = &t
|
||||
}
|
||||
}
|
||||
if toStr := c.Query("to"); toStr != "" {
|
||||
if t, err := time.ParseInLocation("2006-01-02", toStr, time.Local); err == nil {
|
||||
to = &t
|
||||
}
|
||||
}
|
||||
subject := c.Query("subject")
|
||||
|
||||
assignments, err := h.assignmentService.GetForExport(userID, from, to, subject)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "エクスポートに失敗しました")
|
||||
return
|
||||
}
|
||||
|
||||
filename := "assignments_" + time.Now().Format("20060102") + ".csv"
|
||||
c.Header("Content-Type", "text/csv; charset=utf-8")
|
||||
c.Header("Content-Disposition", "attachment; filename=\""+filename+"\"")
|
||||
|
||||
w := csv.NewWriter(c.Writer)
|
||||
// UTF-8 BOM for Excel compatibility
|
||||
c.Writer.Write([]byte("\xef\xbb\xbf"))
|
||||
|
||||
headers := []string{"ID", "タイトル", "科目", "説明", "重要度", "提出期限", "完了", "完了日時", "登録日時"}
|
||||
w.Write(headers)
|
||||
|
||||
priorityLabel := map[string]string{"low": "低", "medium": "中", "high": "高"}
|
||||
for _, a := range assignments {
|
||||
completed := "未完了"
|
||||
if a.IsCompleted {
|
||||
completed = "完了"
|
||||
}
|
||||
completedAt := ""
|
||||
if a.CompletedAt != nil {
|
||||
completedAt = a.CompletedAt.Format("2006/01/02 15:04")
|
||||
}
|
||||
label := priorityLabel[a.Priority]
|
||||
if label == "" {
|
||||
label = a.Priority
|
||||
}
|
||||
w.Write([]string{
|
||||
strconv.FormatUint(uint64(a.ID), 10),
|
||||
a.Title,
|
||||
a.Subject,
|
||||
a.Description,
|
||||
label,
|
||||
a.DueDate.Format("2006/01/02 15:04"),
|
||||
completed,
|
||||
completedAt,
|
||||
a.CreatedAt.Format("2006/01/02 15:04"),
|
||||
})
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) StopRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
@@ -580,6 +658,12 @@ func (h *AssignmentHandler) UpdateRecurring(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
generationLeadDays := 0
|
||||
if v, err := strconv.Atoi(c.PostForm("generation_lead_days")); err == nil && v >= 0 {
|
||||
generationLeadDays = v
|
||||
}
|
||||
generationLeadTime := c.PostForm("generation_lead_time")
|
||||
|
||||
input := service.UpdateRecurringInput{
|
||||
Title: &title,
|
||||
Description: &description,
|
||||
@@ -594,6 +678,8 @@ func (h *AssignmentHandler) UpdateRecurring(c *gin.Context) {
|
||||
EndCount: endCount,
|
||||
EndDate: endDate,
|
||||
EditBehavior: editBehavior,
|
||||
GenerationLeadDays: &generationLeadDays,
|
||||
GenerationLeadTime: &generationLeadTime,
|
||||
}
|
||||
|
||||
_, err = h.recurringService.Update(userID, uint(id), input)
|
||||
|
||||
@@ -21,6 +21,15 @@ func (r *AssignmentRepository) Create(assignment *models.Assignment) error {
|
||||
return r.db.Create(assignment).Error
|
||||
}
|
||||
|
||||
func (r *AssignmentRepository) FindByRecurringAndDue(recurringID uint, dueDate time.Time) (*models.Assignment, error) {
|
||||
var a models.Assignment
|
||||
err := r.db.Where("recurring_assignment_id = ? AND due_date = ?", recurringID, dueDate).First(&a).Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return &a, err
|
||||
}
|
||||
|
||||
func (r *AssignmentRepository) FindByID(id uint) (*models.Assignment, error) {
|
||||
var assignment models.Assignment
|
||||
err := r.db.First(&assignment, id).Error
|
||||
@@ -336,6 +345,22 @@ func (r *AssignmentRepository) GetArchivedSubjects(userID uint) ([]string, error
|
||||
return subjects, err
|
||||
}
|
||||
|
||||
func (r *AssignmentRepository) FindForExport(userID uint, from, to *time.Time, subject string) ([]models.Assignment, error) {
|
||||
var assignments []models.Assignment
|
||||
q := r.db.Where("user_id = ?", userID)
|
||||
if from != nil {
|
||||
q = q.Where("due_date >= ?", *from)
|
||||
}
|
||||
if to != nil {
|
||||
q = q.Where("due_date < ?", to.AddDate(0, 0, 1))
|
||||
}
|
||||
if subject != "" {
|
||||
q = q.Where("subject = ?", subject)
|
||||
}
|
||||
err := q.Order("due_date ASC").Find(&assignments).Error
|
||||
return assignments, err
|
||||
}
|
||||
|
||||
func (r *AssignmentRepository) GetSubjectsByUserIDWithArchived(userID uint, includeArchived bool) ([]string, error) {
|
||||
var subjects []string
|
||||
query := r.db.Model(&models.Assignment{}).
|
||||
|
||||
@@ -248,6 +248,7 @@ func Setup(cfg *config.Config) *gin.Engine {
|
||||
auth.POST("/assignments/:id/toggle", assignmentHandler.Toggle)
|
||||
auth.POST("/assignments/:id/delete", assignmentHandler.Delete)
|
||||
|
||||
auth.GET("/assignments/export", assignmentHandler.ExportCSV)
|
||||
auth.GET("/statistics", assignmentHandler.Statistics)
|
||||
auth.POST("/statistics/archive-subject", assignmentHandler.ArchiveSubject)
|
||||
auth.POST("/statistics/unarchive-subject", assignmentHandler.UnarchiveSubject)
|
||||
|
||||
@@ -378,6 +378,10 @@ func (s *AssignmentService) GetStatistics(userID uint, filter StatisticsFilter)
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetForExport(userID uint, from, to *time.Time, subject string) ([]models.Assignment, error) {
|
||||
return s.assignmentRepo.FindForExport(userID, from, to, subject)
|
||||
}
|
||||
|
||||
func (s *AssignmentService) ArchiveSubject(userID uint, subject string) error {
|
||||
return s.assignmentRepo.ArchiveBySubject(userID, subject)
|
||||
}
|
||||
|
||||
@@ -413,11 +413,6 @@ func (s *RecurringAssignmentService) generateNextIfPending(recurring *models.Rec
|
||||
return nil
|
||||
}
|
||||
|
||||
pendingCount, err := s.recurringRepo.CountPendingByRecurringID(recurring.ID)
|
||||
if err != nil || pendingCount > 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
latest, err := s.recurringRepo.GetLatestAssignmentByRecurringID(recurring.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -426,6 +421,10 @@ func (s *RecurringAssignmentService) generateNextIfPending(recurring *models.Rec
|
||||
return nil
|
||||
}
|
||||
|
||||
if !latest.IsCompleted && !latest.IsOverdue() {
|
||||
return nil
|
||||
}
|
||||
|
||||
nextDueDate := recurring.CalculateNextDueDate(latest.DueDate)
|
||||
|
||||
if recurring.GenerationLeadDays > 0 {
|
||||
|
||||
Reference in New Issue
Block a user