Newer
Older
FanFarm / system / V3fanfarm-ubuntu-local / V3fanfarm-frontend / src.backup.20251007_084354 / components / job / JobDetail.js
@Fanfarm User Fanfarm User on 18 Dec 9 KB add all
// src/components/job/JobDetail.js - コールバック修正版
import React, { useState, useEffect, useContext, useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import styled from 'styled-components';
import { JobPostings, Applications } from '../../api/api';
import { AuthContext } from '../../context/AuthContext';
import Button from '../common/Button';
import Card from '../common/Card';
import JobLocationMap from '../map/JobLocationMap';

// 既存のスタイルコンポーネントはそのまま使用
const JobDetailContainer = styled.div`
  max-width: 800px;
  margin: 40px auto;
  padding: 0 24px;
`;

const JobTitle = styled.h1`
  text-align: center;
  margin-bottom: 24px;
  color: var(--primary-color);
`;

const JobContent = styled.div`
  margin-top: 32px;
`;

const JobSection = styled.div`
  margin-bottom: 24px;
`;

const SectionTitle = styled.h2`
  font-size: 22px;
  margin-bottom: 16px;
  color: var(--secondary-color);
`;

const JobDetailItem = styled.p`
  margin-bottom: 12px;
  font-size: 18px;
  line-height: 1.6;
`;

const JobLabel = styled.span`
  font-weight: 700;
  margin-right: 8px;
`;

const ButtonGroup = styled.div`
  display: flex;
  justify-content: center;
  gap: 16px;
  margin-top: 32px;

  @media (max-width: 768px) {
    flex-direction: column;
    gap: 8px;
  }
`;

const ErrorMessage = styled.p`
  color: var(--error-color);
  font-size: 16px;
  margin-top: 16px;
  text-align: center;
`;

const SuccessMessage = styled.p`
  color: var(--success-color);
  font-size: 18px;
  font-weight: 700;
  margin-top: 16px;
  text-align: center;
`;

const Loading = styled.p`
  text-align: center;
  font-size: 18px;
  color: var(--text-light);
  margin: 48px 0;
`;

const Error = styled.p`
  text-align: center;
  font-size: 18px;
  color: var(--error-color);
  margin: 48px 0;
`;

const Modal = styled.div`
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
`;

const ModalContent = styled(Card)`
  width: 90%;
  max-width: 500px;
  padding: 32px;
  text-align: center;
`;

const ModalTitle = styled.h2`
  margin-bottom: 24px;
  color: var(--primary-color);
`;

const ModalText = styled.p`
  font-size: 18px;
  margin-bottom: 24px;
`;

const MapSection = styled(JobSection)`
  border: 1px solid #e0e0e0;
  border-radius: var(--border-radius);
  padding: 20px;
  background: #fafafa;
`;

const LocationInfo = styled.div`
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 16px;
  font-size: 16px;
  color: var(--text-color);
`;

const JobDetail = () => {
  const { id } = useParams();
  const { currentUser } = useContext(AuthContext);
  const navigate = useNavigate();
  const [job, setJob] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [successMessage, setSuccessMessage] = useState('');
  const [errorMessage, setErrorMessage] = useState('');
  const [showConfirmModal, setShowConfirmModal] = useState(false);
  const [locationData, setLocationData] = useState(null);

  useEffect(() => {
    const fetchJobDetail = async () => {
      try {
        const response = await JobPostings.getById(id);
        setJob(response.data);
        setLoading(false);
      } catch (err) {
        setError('求人情報の取得に失敗しました');
        setLoading(false);
      }
    };

    fetchJobDetail();
  }, [id]);

  const handleApply = () => {
    if (!currentUser) {
      navigate('/login');
      return;
    }

    // 農家ユーザーは応募できない
    if (currentUser.user_type === 'farmer') {
      setErrorMessage('農家ユーザーは求人に応募できません');
      return;
    }

    // 応募確認モーダルを表示
    setShowConfirmModal(true);
  };

  const handleConfirmApply = async () => {
    try {
      await Applications.apply({
        job_posting_id: job.id,
        applicant_id: currentUser.id
      });
      
      setSuccessMessage('応募が完了しました!');
      setShowConfirmModal(false);
      
      // 応募完了後、プロフィールページにリダイレクト
      setTimeout(() => {
        navigate('/profile');
      }, 2000);
    } catch (err) {
      setErrorMessage(err.response?.data?.error || '応募に失敗しました');
      setShowConfirmModal(false);
    }
  };

  const handleCancelApply = () => {
    setShowConfirmModal(false);
  };

  const handleBack = () => {
    navigate('/jobs');
  };

  // 🔧 修正: useCallbackで安定化したコールバック関数
  const handleLocationFound = useCallback((result) => {
    setLocationData(result);
    console.log('📍 求人地の位置情報を取得:', result);
  }, []);

  const handleLocationError = useCallback((error) => {
    console.warn('📍 求人地の位置情報取得エラー:', error);
  }, []);

  if (loading) {
    return (
      <JobDetailContainer>
        <Loading>読み込み中...</Loading>
      </JobDetailContainer>
    );
  }

  if (error) {
    return (
      <JobDetailContainer>
        <Error>{error}</Error>
        <ButtonGroup>
          <Button onClick={handleBack}>求人一覧に戻る</Button>
        </ButtonGroup>
      </JobDetailContainer>
    );
  }

  if (!job) {
    return (
      <JobDetailContainer>
        <Error>求人情報が見つかりませんでした</Error>
        <ButtonGroup>
          <Button onClick={handleBack}>求人一覧に戻る</Button>
        </ButtonGroup>
      </JobDetailContainer>
    );
  }

  return (
    <JobDetailContainer>
      <Card>
        <JobTitle>{job.company_name}</JobTitle>
        
        <JobContent>
          <JobSection>
            <SectionTitle>勤務情報</SectionTitle>
            <JobDetailItem>
              <JobLabel>勤務地:</JobLabel>
              {job.location}
            </JobDetailItem>
            <JobDetailItem>
              <JobLabel>時給:</JobLabel>
              {job.hourly_wage}円
            </JobDetailItem>
            <JobDetailItem>
              <JobLabel>勤務曜日:</JobLabel>
              {job.work_days}
            </JobDetailItem>
            <JobDetailItem>
              <JobLabel>勤務時間:</JobLabel>
              {job.work_hours}
            </JobDetailItem>
            {job.seasonal_vegetables && (
              <JobDetailItem>
                <JobLabel>旬の野菜:</JobLabel>
                {job.seasonal_vegetables}
              </JobDetailItem>
            )}
            <JobDetailItem>
              <JobLabel>募集人数:</JobLabel>
              {job.positions_available}人 (残り{job.positions_available - job.positions_filled}人)
            </JobDetailItem>
          </JobSection>

          {/* 🔧 修正: 改良されたマップセクション */}
          <MapSection>
            <SectionTitle>📍 勤務地マップ</SectionTitle>
            <LocationInfo>
              <span>🏠</span>
              <span>{job.location}</span>
              {locationData && (
                <span style={{ color: '#666', fontSize: '14px' }}>
                  (精度: {Math.round(locationData.accuracy)}%)
                </span>
              )}
            </LocationInfo>
            <JobLocationMap 
              address={job.location} 
              height="350px"
              zoom={15}
              showAccuracy={true}
              enableRetry={true}
              onLocationFound={handleLocationFound}
              onLocationError={handleLocationError}
            />
            {locationData && locationData.note && (
              <div style={{ 
                fontSize: '14px', 
                color: '#666', 
                marginTop: '8px',
                fontStyle: 'italic'
              }}>
                ℹ️ {locationData.note}
              </div>
            )}
          </MapSection>
          
          <JobSection>
            <SectionTitle>業務内容</SectionTitle>
            <JobDetailItem>{job.job_description}</JobDetailItem>
          </JobSection>
          
          <JobSection>
            <SectionTitle>連絡先</SectionTitle>
            <JobDetailItem>
              <JobLabel>連絡先:</JobLabel>
              {job.contact}
            </JobDetailItem>
          </JobSection>
          
          <ButtonGroup>
            <Button onClick={handleApply} disabled={job.positions_filled >= job.positions_available}>
              {job.positions_filled >= job.positions_available ? '募集終了' : '応募する'}
            </Button>
            <Button $secondary onClick={handleBack}>
              求人一覧に戻る
            </Button>
          </ButtonGroup>
          
          {errorMessage && <ErrorMessage>{errorMessage}</ErrorMessage>}
          {successMessage && <SuccessMessage>{successMessage}</SuccessMessage>}
        </JobContent>
      </Card>
      
      {showConfirmModal && (
        <Modal>
          <ModalContent>
            <ModalTitle>応募の確認</ModalTitle>
            <ModalText>
              {job.company_name}の求人に応募します。よろしいですか?
            </ModalText>
            <ButtonGroup>
              <Button onClick={handleConfirmApply}>応募する</Button>
              <Button $secondary onClick={handleCancelApply}>キャンセル</Button>
            </ButtonGroup>
          </ModalContent>
        </Modal>
      )}
    </JobDetailContainer>
  );
};

export default JobDetail;