6.9.0: Задачи-закупки
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m20s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m20s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import './TaskForm.css'
|
||||
const API_URL = '/api/tasks'
|
||||
const PROJECTS_API_URL = '/projects'
|
||||
|
||||
function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = false, returnTo, returnWishlistId }) {
|
||||
function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = false, isPurchase: isPurchaseFromProps = false, returnTo, returnWishlistId }) {
|
||||
const { authFetch } = useAuth()
|
||||
const [name, setName] = useState('')
|
||||
const [progressionBase, setProgressionBase] = useState('')
|
||||
@@ -35,6 +35,10 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
const [maxCards, setMaxCards] = useState('')
|
||||
const [selectedDictionaryIDs, setSelectedDictionaryIDs] = useState([])
|
||||
const [availableDictionaries, setAvailableDictionaries] = useState([])
|
||||
// Purchase-specific state
|
||||
const [isPurchase, setIsPurchase] = useState(isPurchaseFromProps)
|
||||
const [availableBoards, setAvailableBoards] = useState([])
|
||||
const [selectedPurchaseBoards, setSelectedPurchaseBoards] = useState([])
|
||||
const debounceTimer = useRef(null)
|
||||
|
||||
// Загрузка проектов для автокомплита
|
||||
@@ -85,6 +89,22 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
loadDictionaries()
|
||||
}, [])
|
||||
|
||||
// Загрузка досок для закупок
|
||||
useEffect(() => {
|
||||
const loadBoards = async () => {
|
||||
try {
|
||||
const response = await authFetch('/api/purchase/boards-info')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setAvailableBoards(Array.isArray(data.boards) ? data.boards : [])
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading boards for purchase:', err)
|
||||
}
|
||||
}
|
||||
loadBoards()
|
||||
}, [])
|
||||
|
||||
// Функция сброса формы
|
||||
const resetForm = () => {
|
||||
setName('')
|
||||
@@ -399,6 +419,23 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
setMaxCards('')
|
||||
setSelectedDictionaryIDs([])
|
||||
}
|
||||
|
||||
// Загружаем информацию о закупке, если есть purchase_config_id
|
||||
if (data.task.purchase_config_id) {
|
||||
setIsPurchase(true)
|
||||
if (data.purchase_boards && Array.isArray(data.purchase_boards)) {
|
||||
setSelectedPurchaseBoards(data.purchase_boards.map(pb => ({
|
||||
board_id: pb.board_id,
|
||||
group_name: pb.group_name || null
|
||||
})))
|
||||
}
|
||||
// Закупки не могут иметь прогрессию и подзадачи
|
||||
setProgressionBase('')
|
||||
setSubtasks([])
|
||||
} else {
|
||||
setIsPurchase(false)
|
||||
setSelectedPurchaseBoards([])
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
} finally {
|
||||
@@ -413,6 +450,13 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
}
|
||||
}, [isTest])
|
||||
|
||||
// Очистка подзадач при переключении задачи в режим закупки
|
||||
useEffect(() => {
|
||||
if (isPurchase && subtasks.length > 0) {
|
||||
setSubtasks([])
|
||||
}
|
||||
}, [isPurchase])
|
||||
|
||||
// Пересчет rewards при изменении reward_message (debounce)
|
||||
useEffect(() => {
|
||||
if (debounceTimer.current) {
|
||||
@@ -597,6 +641,13 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
}
|
||||
}
|
||||
|
||||
// Валидация закупки
|
||||
if (isPurchase && selectedPurchaseBoards.length === 0) {
|
||||
setError('Выберите хотя бы одну доску или группу для закупки')
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем, что задача с привязанным желанием не может быть периодической
|
||||
const isLinkedToWishlist = wishlistInfo !== null || (taskId && currentWishlistId)
|
||||
if (isLinkedToWishlist && repetitionPeriodValue && repetitionPeriodValue.trim() !== '') {
|
||||
@@ -711,7 +762,7 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
value: parseFloat(r.value) || 0,
|
||||
use_progression: !!(progressionBase && r.use_progression)
|
||||
})),
|
||||
subtasks: isTest ? [] : subtasks.map((st, index) => ({
|
||||
subtasks: (isTest || isPurchase) ? [] : subtasks.map((st, index) => ({
|
||||
id: st.id || undefined,
|
||||
name: st.name.trim() || null,
|
||||
reward_message: st.reward_message.trim() || null,
|
||||
@@ -727,7 +778,10 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
is_test: isTest,
|
||||
words_count: isTest ? parseInt(wordsCount, 10) : undefined,
|
||||
max_cards: isTest && maxCards ? parseInt(maxCards, 10) : undefined,
|
||||
dictionary_ids: isTest ? selectedDictionaryIDs : undefined
|
||||
dictionary_ids: isTest ? selectedDictionaryIDs : undefined,
|
||||
// Purchase-specific fields
|
||||
is_purchase: isPurchase,
|
||||
purchase_boards: isPurchase ? selectedPurchaseBoards : undefined
|
||||
}
|
||||
|
||||
const url = taskId ? `${API_URL}/${taskId}` : API_URL
|
||||
@@ -974,6 +1028,68 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Purchase-specific fields */}
|
||||
{isPurchase && (
|
||||
<div className="form-group test-config-section">
|
||||
<label>Настройка закупки</label>
|
||||
<div style={{ marginTop: '0.5rem' }}>
|
||||
<label style={{ fontSize: '0.875rem', fontWeight: 500, color: '#374151', marginBottom: '0.5rem', display: 'block' }}>Доски и группы *</label>
|
||||
<div className="test-dictionaries-list">
|
||||
{availableBoards.map(board => (
|
||||
<div key={board.id}>
|
||||
<label className="test-dictionary-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedPurchaseBoards.some(pb => pb.board_id === board.id && pb.group_name === null)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
// Добавляем всю доску, убираем отдельные группы этой доски
|
||||
setSelectedPurchaseBoards(prev => [
|
||||
...prev.filter(pb => pb.board_id !== board.id),
|
||||
{ board_id: board.id, group_name: null }
|
||||
])
|
||||
} else {
|
||||
// Убираем доску целиком
|
||||
setSelectedPurchaseBoards(prev => prev.filter(pb => !(pb.board_id === board.id && pb.group_name === null)))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className="test-dictionary-name">{board.name}</span>
|
||||
<span className="test-dictionary-count">(вся доска)</span>
|
||||
</label>
|
||||
{board.groups.length > 0 && !selectedPurchaseBoards.some(pb => pb.board_id === board.id && pb.group_name === null) && (
|
||||
<div style={{ paddingLeft: '1.25rem', marginTop: '2px' }}>
|
||||
{board.groups.map(group => (
|
||||
<label key={group || '__ungrouped'} className="test-dictionary-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedPurchaseBoards.some(pb => pb.board_id === board.id && pb.group_name === (group || ''))}
|
||||
onChange={(e) => {
|
||||
const groupValue = group || ''
|
||||
if (e.target.checked) {
|
||||
setSelectedPurchaseBoards(prev => [...prev, { board_id: board.id, group_name: groupValue }])
|
||||
} else {
|
||||
setSelectedPurchaseBoards(prev => prev.filter(pb => !(pb.board_id === board.id && pb.group_name === groupValue)))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className="test-dictionary-name">{group || 'Остальные'}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{availableBoards.length === 0 && (
|
||||
<div className="test-no-dictionaries">
|
||||
Нет доступных досок. Создайте доску в разделе "Товары".
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!wishlistInfo && (
|
||||
<div className="form-group">
|
||||
<label htmlFor="repetition_period">Повторения</label>
|
||||
@@ -1123,7 +1239,7 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isTest && (
|
||||
{!isTest && !isPurchase && (
|
||||
<div className="form-group">
|
||||
<div className="subtasks-header">
|
||||
<label>Подзадачи</label>
|
||||
|
||||
Reference in New Issue
Block a user