presentation/scheduling/
calendar_view_widget.py 231 lịch tháng (chuyển từ ui/calendar_view.py)
ai_task_creator_dialog.py 208 tạo task bằng AI
task_actions.py 189 thêm/sửa/chạy/xoá/xem log một task
kanban_board_widget.py 98 cột Kanban + vùng thả file
run_history_dialog.py 82 lịch sử các lượt chạy
ai_task_import_dialog.py 81 nhập task từ file
ui/schedule_task_tab.py 297 dựng bảng + đổi chế độ xem
ui/calendar_view.py 10 vỏ chuyển tiếp
Plan ghi 4 file; thực tế cần 6. Hai file thêm là run_history_dialog.py và
task_actions.py — không tách thì schedule_task_tab.py còn 517 dòng, vẫn vượt
ngưỡng 400.
ai_task_import_dialog.py làm mixin chứ không phải hộp thoại rời: plan gọi nó
là dialog, nhưng thực tế nó là TAB THỨ HAI của cùng hộp thoại tạo task, dùng
chung phần xem trước và nút Xác nhận. Tách hẳn thì phải nhân đôi cả hai.
LẠI LỖI DECORATOR: script này tôi quên dùng bản có tính dòng @, nên một
@staticmethod bị bỏ lại mồ côi -> IndentationError. Đây là lần thứ tư cùng
một lỗi. Đã thêm bước dọn decorator mồ côi vào script.
756 test xanh. 16 checker chạy đều qua.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 lines
3.4 KiB
Python
82 lines
3.4 KiB
Python
"""Nhập task từ file — R08-T11.
|
|
|
|
Tab thứ hai của hộp thoại tạo task: tải mẫu về, điền, rồi kéo file vào hoặc
|
|
chọn từ máy. Xem trước nội dung đọc được trước khi tạo, vì một file sai định
|
|
dạng có thể sinh ra hàng chục task rác.
|
|
|
|
Là mixin chứ không phải hộp thoại rời: nó dùng chung phần xem trước và nút
|
|
Xác nhận với tab tạo bằng AI, tách hẳn thì phải nhân đôi cả hai.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
from PySide6.QtCore import Qt, Signal
|
|
from PySide6.QtWidgets import (
|
|
QAbstractItemView, QComboBox, QDialog, QDialogButtonBox, QHBoxLayout,
|
|
QLabel, QLineEdit, QListWidget, QListWidgetItem, QMenu, QMessageBox,
|
|
QPlainTextEdit, QPushButton, QScrollArea, QSizePolicy, QStackedWidget,
|
|
QTabBar, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget,
|
|
)
|
|
from ...core import tasks as taskrepo
|
|
from ...core.projects import list_projects
|
|
from ...core.tasks import STATUSES, chain_error, duplicate_task, new_task
|
|
from ...core.worker import AgentWorker
|
|
from ...i18n import on_language_changed, tr
|
|
from ...state import AppContext
|
|
from ...theme import current_palette
|
|
from ...ui.calendar_view import CalendarView
|
|
from ...ui.icons import icon
|
|
from ...ui.osutil import open_path
|
|
from .kanban_board_widget import _DropZone
|
|
|
|
|
|
class TaskImportMixin:
|
|
"""Nhập task từ file. Trộn vào _AiCreateDialog."""
|
|
|
|
def _export_template(self) -> None:
|
|
from PySide6.QtWidgets import QFileDialog
|
|
|
|
from ..core.task_excel import export_template
|
|
|
|
path, _ = QFileDialog.getSaveFileName(
|
|
self, tr("schedtask.export_template_btn"),
|
|
"cowork_tasks_template.xlsx", "Excel (*.xlsx)")
|
|
if not path:
|
|
return
|
|
try:
|
|
export_template(path)
|
|
open_path(str(Path(path).parent))
|
|
except Exception as exc: # noqa: BLE001
|
|
QMessageBox.warning(self, tr("schedtask.tab_import"), str(exc))
|
|
def _pick_import_file(self) -> None:
|
|
from PySide6.QtWidgets import QFileDialog
|
|
|
|
from ..core.task_import import IMPORT_FILTER
|
|
|
|
path, _ = QFileDialog.getOpenFileName(
|
|
self, tr("schedtask.import_pick_btn"), "", IMPORT_FILTER)
|
|
if path:
|
|
self._load_import_file(path)
|
|
def _load_import_file(self, path: str) -> None:
|
|
from ..core.task_import import import_tasks
|
|
|
|
try:
|
|
self._planned = import_tasks(path)
|
|
except ValueError as exc:
|
|
self.import_preview.setPlainText(str(exc))
|
|
self.buttons.button(QDialogButtonBox.Ok).setEnabled(False)
|
|
return
|
|
by_id = {t["task_id"]: t["title"] for t in self._planned}
|
|
lines = []
|
|
for i, t in enumerate(self._planned, 1):
|
|
sched = t.get("schedule", {})
|
|
when = sched.get("run_at") if sched.get("enabled") else tr("schedtask.no_schedule")
|
|
deps = t.get("dependency", {}).get("depends_on") or []
|
|
dep_note = (" ← depends: " + ", ".join(by_id.get(d, "?") for d in deps)) if deps else ""
|
|
lines.append(f"{i}. [{t.get('task_type')}] {t.get('title')}\n"
|
|
f" {when} repeat={sched.get('repeat_type', 'none')}{dep_note}")
|
|
self.import_preview.setPlainText("\n\n".join(lines))
|
|
self.buttons.button(QDialogButtonBox.Ok).setEnabled(bool(self._planned))
|