Работа со стейтем в функциональных и классовых компонентах немного отличается, в том плане, что в классовых ты работаешь с полным объектом, типа такого:
this.state = { done: false, title: "Some title", }
и можешь обращаться напрямую к его свойствам типа this.state.done

В функциональных же ты не напрямую со стейтом работаешь, а с его свойствами типа так:
const [done] = useState(false)
Во втором случае у тебя стейт как бы всегда отсутствует, есть только его значения (которые могут быть пустыми). В первом же случае (в классовых) у тебя стейт может не быть, а может и быть. То есть если ты раз задал this.setState({done: true}), то у тебя this.state уже есть всегда в течение жизни компонента. Таким образом у тебя логическая ошибка в этом условии:
if (this.state) { this.setState({ done: true, }); }
У тебя всегда есть this.state и получается, что всегда задается done: true. То есть надо было проверять
if (!this.state) { this.setState({ done: true, }); }
то есть проверять отсутствие стейта, а не его наличие. Ну, это в твоем случае. А вообще в таких случаях делают так:

handleCardClick = () => { this.setState({ done: !this.state?.done, }) }
То есть устанавливают инвертированное значение.