本来の期限に加えて自分の中での目標期限を設定できるよう仕様追加
This commit is contained in:
@@ -239,6 +239,7 @@ type CreateAssignmentInput struct {
|
||||
Subject string `json:"subject"`
|
||||
Priority string `json:"priority"`
|
||||
DueDate string `json:"due_date" binding:"required"`
|
||||
SoftDueDate string `json:"soft_due_date"`
|
||||
|
||||
ReminderEnabled bool `json:"reminder_enabled"`
|
||||
ReminderAt string `json:"reminder_at"`
|
||||
@@ -352,7 +353,17 @@ func (h *APIHandler) CreateAssignment(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
assignment, err := h.assignmentService.Create(userID, input.Title, input.Description, input.Subject, input.Priority, dueDate, input.ReminderEnabled, reminderAt, urgentReminder)
|
||||
var softDueDate *time.Time
|
||||
if input.SoftDueDate != "" {
|
||||
parsedSoft, err := parseDateString(input.SoftDueDate)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid soft_due_date format"})
|
||||
return
|
||||
}
|
||||
softDueDate = &parsedSoft
|
||||
}
|
||||
|
||||
assignment, err := h.assignmentService.Create(userID, input.Title, input.Description, input.Subject, input.Priority, dueDate, softDueDate, input.ReminderEnabled, reminderAt, urgentReminder)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create assignment"})
|
||||
return
|
||||
@@ -367,6 +378,7 @@ type UpdateAssignmentInput struct {
|
||||
Subject string `json:"subject"`
|
||||
Priority string `json:"priority"`
|
||||
DueDate string `json:"due_date"`
|
||||
SoftDueDate string `json:"soft_due_date"`
|
||||
ReminderEnabled *bool `json:"reminder_enabled"`
|
||||
ReminderAt string `json:"reminder_at"`
|
||||
UrgentReminderEnabled *bool `json:"urgent_reminder_enabled"`
|
||||
@@ -448,7 +460,17 @@ func (h *APIHandler) UpdateAssignment(c *gin.Context) {
|
||||
urgentReminderEnabled = *input.UrgentReminderEnabled
|
||||
}
|
||||
|
||||
assignment, err := h.assignmentService.Update(userID, uint(id), title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
softDueDate := existing.SoftDueDate
|
||||
if input.SoftDueDate != "" {
|
||||
parsedSoft, err := parseDateString(input.SoftDueDate)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid soft_due_date format"})
|
||||
return
|
||||
}
|
||||
softDueDate = &parsedSoft
|
||||
}
|
||||
|
||||
assignment, err := h.assignmentService.Update(userID, uint(id), title, description, subject, priority, dueDate, softDueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update assignment"})
|
||||
return
|
||||
|
||||
@@ -111,6 +111,7 @@ func (h *AssignmentHandler) New(c *gin.Context) {
|
||||
now := time.Now()
|
||||
tomorrow := now.AddDate(0, 0, 1)
|
||||
defaultDue := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 23, 59, 0, 0, now.Location())
|
||||
defaultSoftDue := defaultDue.Add(-48 * time.Hour)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "assignments/new.html", gin.H{
|
||||
"title": "課題登録",
|
||||
@@ -119,6 +120,7 @@ func (h *AssignmentHandler) New(c *gin.Context) {
|
||||
"currentWeekday": int(now.Weekday()),
|
||||
"currentDay": now.Day(),
|
||||
"defaultDueDate": defaultDue.Format("2006-01-02T15:04"),
|
||||
"defaultSoftDueDate": defaultSoftDue.Format("2006-01-02T15:04"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -178,6 +180,16 @@ func (h *AssignmentHandler) Create(c *gin.Context) {
|
||||
dueDate = dueDate.Add(23*time.Hour + 59*time.Minute)
|
||||
}
|
||||
|
||||
var softDueDate *time.Time
|
||||
if softDueDateStr := c.PostForm("soft_due_date"); softDueDateStr != "" {
|
||||
if parsed, err := time.ParseInLocation("2006-01-02T15:04", softDueDateStr, time.Local); err == nil {
|
||||
softDueDate = &parsed
|
||||
} else if parsed, err := time.ParseInLocation("2006-01-02", softDueDateStr, time.Local); err == nil {
|
||||
p := parsed.Add(23*time.Hour + 59*time.Minute)
|
||||
softDueDate = &p
|
||||
}
|
||||
}
|
||||
|
||||
recurrenceType := c.PostForm("recurrence_type")
|
||||
if recurrenceType != "" && recurrenceType != "none" {
|
||||
|
||||
@@ -265,7 +277,7 @@ func (h *AssignmentHandler) Create(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
assignment, err := h.assignmentService.Create(userID, title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
assignment, err := h.assignmentService.Create(userID, title, description, subject, priority, dueDate, softDueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
if err != nil {
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
@@ -352,7 +364,17 @@ func (h *AssignmentHandler) Update(c *gin.Context) {
|
||||
dueDate = dueDate.Add(23*time.Hour + 59*time.Minute)
|
||||
}
|
||||
|
||||
_, err = h.assignmentService.Update(userID, uint(id), title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
var softDueDate *time.Time
|
||||
if softDueDateStr := c.PostForm("soft_due_date"); softDueDateStr != "" {
|
||||
if parsed, err := time.ParseInLocation("2006-01-02T15:04", softDueDateStr, time.Local); err == nil {
|
||||
softDueDate = &parsed
|
||||
} else if parsed, err := time.ParseInLocation("2006-01-02", softDueDateStr, time.Local); err == nil {
|
||||
p := parsed.Add(23*time.Hour + 59*time.Minute)
|
||||
softDueDate = &p
|
||||
}
|
||||
}
|
||||
|
||||
_, err = h.assignmentService.Update(userID, uint(id), title, description, subject, priority, dueDate, softDueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
if err != nil {
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
return
|
||||
@@ -501,10 +523,9 @@ func (h *AssignmentHandler) ExportCSV(c *gin.Context) {
|
||||
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", "タイトル", "科目", "説明", "重要度", "提出期限", "完了", "完了日時", "登録日時"}
|
||||
headers := []string{"ID", "タイトル", "科目", "説明", "重要度", "提出期限", "自分の期限", "完了", "完了日時", "登録日時"}
|
||||
w.Write(headers)
|
||||
|
||||
priorityLabel := map[string]string{"low": "低", "medium": "中", "high": "高"}
|
||||
@@ -517,6 +538,10 @@ func (h *AssignmentHandler) ExportCSV(c *gin.Context) {
|
||||
if a.CompletedAt != nil {
|
||||
completedAt = a.CompletedAt.Format("2006/01/02 15:04")
|
||||
}
|
||||
softDueDateStr := ""
|
||||
if a.SoftDueDate != nil {
|
||||
softDueDateStr = a.SoftDueDate.Format("2006/01/02 15:04")
|
||||
}
|
||||
label := priorityLabel[a.Priority]
|
||||
if label == "" {
|
||||
label = a.Priority
|
||||
@@ -528,6 +553,7 @@ func (h *AssignmentHandler) ExportCSV(c *gin.Context) {
|
||||
a.Description,
|
||||
label,
|
||||
a.DueDate.Format("2006/01/02 15:04"),
|
||||
softDueDateStr,
|
||||
completed,
|
||||
completedAt,
|
||||
a.CreatedAt.Format("2006/01/02 15:04"),
|
||||
|
||||
@@ -14,6 +14,7 @@ type Assignment struct {
|
||||
Subject string `json:"subject"`
|
||||
Priority string `gorm:"not null;default:medium" json:"priority"`
|
||||
DueDate time.Time `gorm:"not null" json:"due_date"`
|
||||
SoftDueDate *time.Time `json:"soft_due_date,omitempty"`
|
||||
IsCompleted bool `gorm:"default:false" json:"is_completed"`
|
||||
IsArchived bool `gorm:"default:false;index" json:"is_archived"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
@@ -23,7 +24,6 @@ type Assignment struct {
|
||||
UrgentReminderEnabled bool `gorm:"default:true" json:"urgent_reminder_enabled"`
|
||||
LastUrgentReminderSent *time.Time `json:"last_urgent_reminder_sent,omitempty"`
|
||||
|
||||
// Recurring assignment reference
|
||||
RecurringAssignmentID *uint `gorm:"index" json:"recurring_assignment_id,omitempty"`
|
||||
RecurringAssignment *RecurringAssignment `gorm:"foreignKey:RecurringAssignmentID" json:"-"`
|
||||
|
||||
@@ -34,6 +34,13 @@ type Assignment struct {
|
||||
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||
}
|
||||
|
||||
func (a *Assignment) GetEffectiveSoftDueDate() time.Time {
|
||||
if a.SoftDueDate != nil {
|
||||
return *a.SoftDueDate
|
||||
}
|
||||
return a.DueDate.Add(-48 * time.Hour)
|
||||
}
|
||||
|
||||
func (a *Assignment) IsOverdue() bool {
|
||||
return !a.IsCompleted && time.Now().After(a.DueDate)
|
||||
}
|
||||
|
||||
@@ -25,11 +25,27 @@ func getFuncMap() template.FuncMap {
|
||||
"formatDate": func(t time.Time) string {
|
||||
return t.Format("2006/01/02")
|
||||
},
|
||||
"formatDateTime": func(t time.Time) string {
|
||||
return t.Format("2006/01/02 15:04")
|
||||
"formatDateTime": func(t interface{}) string {
|
||||
switch v := t.(type) {
|
||||
case time.Time:
|
||||
return v.Format("2006/01/02 15:04")
|
||||
case *time.Time:
|
||||
if v != nil {
|
||||
return v.Format("2006/01/02 15:04")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"formatDateInput": func(t time.Time) string {
|
||||
return t.Format("2006-01-02T15:04")
|
||||
"formatDateInput": func(t interface{}) string {
|
||||
switch v := t.(type) {
|
||||
case time.Time:
|
||||
return v.Format("2006-01-02T15:04")
|
||||
case *time.Time:
|
||||
if v != nil {
|
||||
return v.Format("2006-01-02T15:04")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"isOverdue": func(t time.Time, completed bool) bool {
|
||||
return !completed && time.Now().After(t)
|
||||
|
||||
@@ -31,7 +31,7 @@ func NewAssignmentService() *AssignmentService {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AssignmentService) Create(userID uint, title, description, subject, priority string, dueDate time.Time, reminderEnabled bool, reminderAt *time.Time, urgentReminderEnabled bool) (*models.Assignment, error) {
|
||||
func (s *AssignmentService) Create(userID uint, title, description, subject, priority string, dueDate time.Time, softDueDate *time.Time, reminderEnabled bool, reminderAt *time.Time, urgentReminderEnabled bool) (*models.Assignment, error) {
|
||||
if priority == "" {
|
||||
priority = "medium"
|
||||
}
|
||||
@@ -42,6 +42,7 @@ func (s *AssignmentService) Create(userID uint, title, description, subject, pri
|
||||
Subject: subject,
|
||||
Priority: priority,
|
||||
DueDate: dueDate,
|
||||
SoftDueDate: softDueDate,
|
||||
IsCompleted: false,
|
||||
ReminderEnabled: reminderEnabled,
|
||||
ReminderAt: reminderAt,
|
||||
@@ -195,7 +196,7 @@ func (s *AssignmentService) SearchAssignments(userID uint, query, priority, filt
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AssignmentService) Update(userID, assignmentID uint, title, description, subject, priority string, dueDate time.Time, reminderEnabled bool, reminderAt *time.Time, urgentReminderEnabled bool) (*models.Assignment, error) {
|
||||
func (s *AssignmentService) Update(userID, assignmentID uint, title, description, subject, priority string, dueDate time.Time, softDueDate *time.Time, reminderEnabled bool, reminderAt *time.Time, urgentReminderEnabled bool) (*models.Assignment, error) {
|
||||
assignment, err := s.GetByID(userID, assignmentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -206,6 +207,7 @@ func (s *AssignmentService) Update(userID, assignmentID uint, title, description
|
||||
assignment.Subject = subject
|
||||
assignment.Priority = priority
|
||||
assignment.DueDate = dueDate
|
||||
assignment.SoftDueDate = softDueDate
|
||||
assignment.ReminderEnabled = reminderEnabled
|
||||
assignment.ReminderAt = reminderAt
|
||||
assignment.UrgentReminderEnabled = urgentReminderEnabled
|
||||
|
||||
@@ -170,10 +170,11 @@ func (s *NotificationService) SendUrgentReminder(userID uint, assignment *models
|
||||
return err
|
||||
}
|
||||
|
||||
timeRemaining := time.Until(assignment.DueDate)
|
||||
softDue := assignment.GetEffectiveSoftDueDate()
|
||||
timeRemaining := time.Until(softDue)
|
||||
var timeStr string
|
||||
if timeRemaining < 0 {
|
||||
timeStr = "期限切れ!"
|
||||
timeStr = "自分の期限切れ!"
|
||||
} else if timeRemaining < time.Hour {
|
||||
timeStr = fmt.Sprintf("あと%d分", int(timeRemaining.Minutes()))
|
||||
} else {
|
||||
@@ -191,12 +192,13 @@ func (s *NotificationService) SendUrgentReminder(userID uint, assignment *models
|
||||
}
|
||||
|
||||
message := fmt.Sprintf(
|
||||
"%s 督促通知!\n\n【%s】\n科目: %s\n期限: %s (%s)\n\n完了したらアプリで完了ボタンを押してください!",
|
||||
"%s 督促通知!\n\n【%s】\n科目: %s\n自分の期限: %s (%s)\nガチ期限: %s\n\n完了したらアプリで完了ボタンを押してください!",
|
||||
priorityEmoji,
|
||||
assignment.Title,
|
||||
assignment.Subject,
|
||||
assignment.DueDate.Format("2006/01/02 15:04"),
|
||||
softDue.Format("2006/01/02 15:04"),
|
||||
timeStr,
|
||||
assignment.DueDate.Format("2006/01/02 15:04"),
|
||||
)
|
||||
|
||||
var errors []string
|
||||
@@ -268,9 +270,10 @@ func (s *NotificationService) ProcessUrgentReminders() {
|
||||
}
|
||||
|
||||
for _, assignment := range assignments {
|
||||
timeUntilDue := assignment.DueDate.Sub(now)
|
||||
softDue := assignment.GetEffectiveSoftDueDate()
|
||||
timeUntilSoftDue := softDue.Sub(now)
|
||||
|
||||
if timeUntilDue > urgentStartTime {
|
||||
if timeUntilSoftDue > urgentStartTime {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -244,26 +244,27 @@ func (s *RecurringAssignmentService) UpdateAssignmentWithBehavior(
|
||||
assignment *models.Assignment,
|
||||
title, description, subject, priority string,
|
||||
dueDate time.Time,
|
||||
softDueDate *time.Time,
|
||||
reminderEnabled bool,
|
||||
reminderAt *time.Time,
|
||||
urgentReminderEnabled bool,
|
||||
editBehavior string,
|
||||
) error {
|
||||
if assignment.RecurringAssignmentID == nil {
|
||||
return s.updateSingleAssignment(assignment, title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
return s.updateSingleAssignment(assignment, title, description, subject, priority, dueDate, softDueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
}
|
||||
|
||||
recurring, err := s.GetByID(userID, *assignment.RecurringAssignmentID)
|
||||
if err != nil {
|
||||
return s.updateSingleAssignment(assignment, title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
return s.updateSingleAssignment(assignment, title, description, subject, priority, dueDate, softDueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
}
|
||||
|
||||
switch editBehavior {
|
||||
case models.EditBehaviorThisOnly:
|
||||
return s.updateSingleAssignment(assignment, title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
return s.updateSingleAssignment(assignment, title, description, subject, priority, dueDate, softDueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
|
||||
case models.EditBehaviorThisAndFuture:
|
||||
if err := s.updateSingleAssignment(assignment, title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled); err != nil {
|
||||
if err := s.updateSingleAssignment(assignment, title, description, subject, priority, dueDate, softDueDate, reminderEnabled, reminderAt, urgentReminderEnabled); err != nil {
|
||||
return err
|
||||
}
|
||||
recurring.Title = title
|
||||
@@ -288,7 +289,7 @@ func (s *RecurringAssignmentService) UpdateAssignmentWithBehavior(
|
||||
return s.updateAllPendingAssignments(recurring.ID, title, description, subject, priority, urgentReminderEnabled)
|
||||
|
||||
default:
|
||||
return s.updateSingleAssignment(assignment, title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
return s.updateSingleAssignment(assignment, title, description, subject, priority, dueDate, softDueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +297,7 @@ func (s *RecurringAssignmentService) updateSingleAssignment(
|
||||
assignment *models.Assignment,
|
||||
title, description, subject, priority string,
|
||||
dueDate time.Time,
|
||||
softDueDate *time.Time,
|
||||
reminderEnabled bool,
|
||||
reminderAt *time.Time,
|
||||
urgentReminderEnabled bool,
|
||||
@@ -305,6 +307,7 @@ func (s *RecurringAssignmentService) updateSingleAssignment(
|
||||
assignment.Subject = subject
|
||||
assignment.Priority = priority
|
||||
assignment.DueDate = dueDate
|
||||
assignment.SoftDueDate = softDueDate
|
||||
assignment.ReminderEnabled = reminderEnabled
|
||||
assignment.ReminderAt = reminderAt
|
||||
assignment.UrgentReminderEnabled = urgentReminderEnabled
|
||||
@@ -471,6 +474,8 @@ func (s *RecurringAssignmentService) generateAssignment(recurring *models.Recurr
|
||||
reminderAt = &t
|
||||
}
|
||||
|
||||
softDue := dueDate.Add(-48 * time.Hour)
|
||||
|
||||
assignment := &models.Assignment{
|
||||
UserID: recurring.UserID,
|
||||
Title: recurring.Title,
|
||||
@@ -478,6 +483,7 @@ func (s *RecurringAssignmentService) generateAssignment(recurring *models.Recurr
|
||||
Subject: recurring.Subject,
|
||||
Priority: recurring.Priority,
|
||||
DueDate: dueDate,
|
||||
SoftDueDate: &softDue,
|
||||
ReminderEnabled: recurring.ReminderEnabled,
|
||||
ReminderAt: reminderAt,
|
||||
UrgentReminderEnabled: recurring.UrgentReminderEnabled,
|
||||
|
||||
@@ -28,9 +28,14 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="due_date" class="form-label">提出期限 <span class="text-danger" aria-hidden="true">*</span><span class="visually-hidden">(必須)</span></label>
|
||||
<label for="due_date" class="form-label">提出期限(ガチ期限) <span class="text-danger" aria-hidden="true">*</span><span class="visually-hidden">(必須)</span></label>
|
||||
<input type="datetime-local" class="form-control" id="due_date" name="due_date" value="{{formatDateInput .assignment.DueDate}}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="soft_due_date" class="form-label">自分の期限 <span class="badge bg-secondary">任意</span></label>
|
||||
<input type="datetime-local" class="form-control" id="soft_due_date" name="soft_due_date" value="{{if .assignment.SoftDueDate}}{{formatDateInput .assignment.SoftDueDate}}{{end}}">
|
||||
<div class="form-text small text-muted">鬼督促はこの期限を基準に通知します。未指定の場合は提出期限の2日前になります。</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">説明</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="3">{{.assignment.Description}}</textarea>
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="small fw-bold text-dark user-select-all">{{.DueDate.Format "2006/01/02 15:04"}}</div>
|
||||
{{if .SoftDueDate}}<div class="small text-info" title="自分の期限"><i class="bi bi-clock-history me-1" aria-hidden="true"></i>{{.SoftDueDate.Format "01/02 15:04"}}</div>{{end}}
|
||||
</td>
|
||||
<td class="countdown-col">
|
||||
{{if not .IsCompleted}}
|
||||
|
||||
@@ -28,9 +28,14 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="due_date" class="form-label">提出期限 <span class="text-danger" aria-hidden="true">*</span><span class="visually-hidden">(必須)</span></label>
|
||||
<label for="due_date" class="form-label">提出期限(ガチ期限) <span class="text-danger" aria-hidden="true">*</span><span class="visually-hidden">(必須)</span></label>
|
||||
<input type="datetime-local" class="form-control" id="due_date" name="due_date" value="{{.defaultDueDate}}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="soft_due_date" class="form-label">自分の期限 <span class="badge bg-secondary">任意</span> <i class="bi bi-question-circle-fill text-muted" data-bs-toggle="tooltip" data-bs-placement="top" title="自分の期限とは、余裕を持って自分でこの期間までに提出するといった目標期限です。デフォルトでは2日前に設定されます。" aria-label="自分の期限の説明"></i></label>
|
||||
<input type="datetime-local" class="form-control" id="soft_due_date" name="soft_due_date" value="{{.defaultSoftDueDate}}">
|
||||
<div class="form-text small text-muted">鬼督促はこの期限を基準に通知します。</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">説明</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="3">{{.description}}</textarea>
|
||||
@@ -156,6 +161,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Bootstrap tooltip初期化
|
||||
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function(el) { new bootstrap.Tooltip(el); });
|
||||
|
||||
function toggleReminderDate(checkbox) {
|
||||
document.getElementById('reminder_at_group').style.display = checkbox.checked ? 'block' : 'none';
|
||||
}
|
||||
@@ -204,5 +212,25 @@
|
||||
document.getElementById('recurringSettings').addEventListener('show.bs.collapse', function () {
|
||||
updateLeadDaysMax();
|
||||
});
|
||||
|
||||
// Auto-sync soft_due_date when due_date changes
|
||||
(function() {
|
||||
var dueDateInput = document.getElementById('due_date');
|
||||
var softDueDateInput = document.getElementById('soft_due_date');
|
||||
var userEditedSoftDue = false;
|
||||
softDueDateInput.addEventListener('input', function() { userEditedSoftDue = true; });
|
||||
dueDateInput.addEventListener('change', function() {
|
||||
if (userEditedSoftDue) return;
|
||||
if (!dueDateInput.value) return;
|
||||
var dueDate = new Date(dueDateInput.value);
|
||||
dueDate.setDate(dueDate.getDate() - 2);
|
||||
var y = dueDate.getFullYear();
|
||||
var m = String(dueDate.getMonth() + 1).padStart(2, '0');
|
||||
var d = String(dueDate.getDate()).padStart(2, '0');
|
||||
var h = String(dueDate.getHours()).padStart(2, '0');
|
||||
var min = String(dueDate.getMinutes()).padStart(2, '0');
|
||||
softDueDateInput.value = y + '-' + m + '-' + d + 'T' + h + ':' + min;
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
{{if eq .Priority "high"}}<span class="badge bg-danger me-1">高<span class="visually-hidden">(重要度:高)</span></span>{{end}}
|
||||
<strong>{{.Title}}</strong>
|
||||
<br><small class="text-danger">{{formatDateTime .DueDate}}</small>
|
||||
{{if .SoftDueDate}}<br><small class="text-info"><i class="bi bi-clock-history" aria-hidden="true"></i> 自分の期限: {{formatDateTime .SoftDueDate}}</small>{{end}}
|
||||
</div>
|
||||
<form action="/assignments/{{.ID}}/toggle" method="POST">
|
||||
<input type="hidden" name="_csrf" value="{{$.csrfToken}}">
|
||||
@@ -140,6 +141,7 @@
|
||||
{{if eq .Priority "high"}}<span class="badge bg-danger me-1">高<span class="visually-hidden">(重要度:高)</span></span>{{end}}
|
||||
<strong>{{.Title}}</strong>
|
||||
<br><small class="text-muted">{{formatDateTime .DueDate}}</small>
|
||||
{{if .SoftDueDate}}<br><small class="text-info"><i class="bi bi-clock-history" aria-hidden="true"></i> 自分の期限: {{formatDateTime .SoftDueDate}}</small>{{end}}
|
||||
</div>
|
||||
<form action="/assignments/{{.ID}}/toggle" method="POST">
|
||||
<input type="hidden" name="_csrf" value="{{$.csrfToken}}">
|
||||
@@ -165,6 +167,7 @@
|
||||
{{if eq .Priority "high"}}<span class="badge bg-danger me-1">高<span class="visually-hidden">(重要度:高)</span></span>{{end}}
|
||||
<strong>{{.Title}}</strong>
|
||||
<br><small class="text-muted">{{formatDateTime .DueDate}}</small>
|
||||
{{if .SoftDueDate}}<br><small class="text-info"><i class="bi bi-clock-history" aria-hidden="true"></i> 自分の期限: {{formatDateTime .SoftDueDate}}</small>{{end}}
|
||||
</div>
|
||||
<form action="/assignments/{{.ID}}/toggle" method="POST">
|
||||
<input type="hidden" name="_csrf" value="{{$.csrfToken}}">
|
||||
|
||||
Reference in New Issue
Block a user